Lotu RadarAbout · RSS

Latest News Archive - Page 640

Developers & Open Source · microsoft/playwright Releases

v1.59.0

<h2>🎬 Screencast</h2> <p>New <a href="https://playwright.dev/docs/api/class-page#page-screencast" rel="nofollow">page.screencast</a> API provides a unified interface for capturing page content with:</p> <ul> <li>Screencast recordings</li> <li>Action annotations</li> <li>Visual overlays</li> <li>Real-time frame capture</li> <li>Agentic video receipts</li> </ul> <p><a target="_blank" rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/microsoft/playwright/main/docs/src/images/release-notes-1.59-screencast-demo.gif"><img src="https://raw.githubusercontent.com/microsoft/playwright/main/docs/src/images/release-notes-1.59-screencast-demo.gif" alt="Demo" width="500" height="313" style="max-width: 100%; height: auto; max-height: 313px;"></a></p> <p><strong>Screencast recording</strong> — record video with precise start/stop control, as an alternative to the <a href="https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-video" rel="nofollow"><code>recordVideo</code></a> option:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="await page.screencast.start({ path: 'video.webm' }); // ... perform actions ... await page.screencast.stop();"><pre><span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">start</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'video.webm'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// ... perform actions ...</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">stop</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p><strong>Action annotations</strong> — enable built-in visual annotations that highlight interacted elements and display action titles during recording:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="await page.screencast.showActions({ position: 'top-right' });"><pre><span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">showActions</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">position</span>: <span class="pl-s">'top-right'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p><a href="https://playwright.dev/docs/api/class-screencast#screencast-show-actions" rel="nofollow">screencast.showActions()</a> accepts <code>position</code> (<code>'top-left'</code>, <code>'top'</code>, <code>'top-right'</code>, <code>'bottom-left'</code>, <code>'bottom'</code>, <code>'bottom-right'</code>), <code>duration</code> (ms per annotation), and <code>fontSize</code> (px). Returns a disposable to stop showing actions.</p> <p>Action annotations can also be enabled in test fixtures via the <code>video</code> option:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// playwright.config.ts export default defineConfig({ use: { video: { mode: 'on', show: { actions: { position: 'top-left' }, test: { position: 'top-right' }, }, }, }, });"><pre><span class="pl-c">// playwright.config.ts</span> <span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-en">defineConfig</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">use</span>: <span class="pl-kos">{</span> <span class="pl-c1">video</span>: <span class="pl-kos">{</span> <span class="pl-c1">mode</span>: <span class="pl-s">'on'</span><span class="pl-kos">,</span> <span class="pl-c1">show</span>: <span class="pl-kos">{</span> <span class="pl-c1">actions</span>: <span class="pl-kos">{</span> <span class="pl-c1">position</span>: <span class="pl-s">'top-left'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-c1">test</span>: <span class="pl-kos">{</span> <span class="pl-c1">position</span>: <span class="pl-s">'top-right'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p><strong>Visual overlays</strong> — add chapter titles and custom HTML overlays on top of the page for richer narration:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="await page.screencast.showChapter('Adding TODOs', { description: 'Type and press enter for each TODO', duration: 1000, }); await page.screencast.showOverlay('<div style="color: red">Recording</div>');"><pre><span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">showChapter</span><span class="pl-kos">(</span><span class="pl-s">'Adding TODOs'</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">description</span>: <span class="pl-s">'Type and press enter for each TODO'</span><span class="pl-kos">,</span> <span class="pl-c1">duration</span>: <span class="pl-c1">1000</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">showOverlay</span><span class="pl-kos">(</span><span class="pl-s">'<div style="color: red">Recording</div>'</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p><strong>Real-time frame capture</strong> — stream JPEG-encoded frames for custom processing like thumbnails, live previews, AI vision, and more:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="await page.screencast.start({ onFrame: ({ data }) => sendToVisionModel(data), size: { width: 800, height: 600 }, });"><pre><span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">start</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-en">onFrame</span>: <span class="pl-kos">(</span><span class="pl-kos">{</span> data <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-en">sendToVisionModel</span><span class="pl-kos">(</span><span class="pl-s1">data</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-c1">size</span>: <span class="pl-kos">{</span> <span class="pl-c1">width</span>: <span class="pl-c1">800</span><span class="pl-kos">,</span> <span class="pl-c1">height</span>: <span class="pl-c1">600</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p><strong>Agentic video receipts</strong> — coding agents can produce video evidence of their work. After completing a task, an agent can record a walkthrough video with rich annotations for human review:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="await page.screencast.start({ path: 'receipt.webm' }); await page.screencast.showActions({ position: 'top-right' }); await page.screencast.showChapter('Verifying checkout flow', { description: 'Added coupon code support per ticket #1234', }); // Agent performs the verification steps... await page.locator('#coupon').fill('SAVE20'); await page.locator('#apply-coupon').click(); await expect(page.locator('.discount')).toContainText('20%'); await page.screencast.showChapter('Done', { description: 'Coupon applied, discount reflected in total', }); await page.screencast.stop();"><pre><span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">start</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'receipt.webm'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">showActions</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">position</span>: <span class="pl-s">'top-right'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">showChapter</span><span class="pl-kos">(</span><span class="pl-s">'Verifying checkout flow'</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">description</span>: <span class="pl-s">'Added coupon code support per ticket #1234'</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// Agent performs the verification steps...</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">locator</span><span class="pl-kos">(</span><span class="pl-s">'#coupon'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">fill</span><span class="pl-kos">(</span><span class="pl-s">'SAVE20'</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">locator</span><span class="pl-kos">(</span><span class="pl-s">'#apply-coupon'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">click</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">locator</span><span class="pl-kos">(</span><span class="pl-s">'.discount'</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toContainText</span><span class="pl-kos">(</span><span class="pl-s">'20%'</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">showChapter</span><span class="pl-kos">(</span><span class="pl-s">'Done'</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">description</span>: <span class="pl-s">'Coupon applied, discount reflected in total'</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">screencast</span><span class="pl-kos">.</span><span class="pl-en">stop</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p>The resulting video serves as a receipt: chapter titles provide context, action annotations highlight each interaction, and the visual walkthrough is faster to review than text logs.</p> <h2>🔗 Interoperability</h2> <p>New <a href="https://playwright.dev/docs/api/class-browser#browser-bind" rel="nofollow">browser.bind()</a> API makes a launched browser available for <code>playwright-cli</code>, <code>@playwright/mcp</code>, and other clients to connect to.</p> <p><strong>Bind a browser</strong> — start a browser and bind it so others can connect:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const { endpoint } = await browser.bind('my-session', { workspaceDir: '/my/project', });"><pre><span class="pl-k">const</span> <span class="pl-kos">{</span> endpoint <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">browser</span><span class="pl-kos">.</span><span class="pl-en">bind</span><span class="pl-kos">(</span><span class="pl-s">'my-session'</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">workspaceDir</span>: <span class="pl-s">'/my/project'</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p><strong>Connect from playwright-cli</strong> — connect to the running browser from your favorite coding agent.</p> <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="playwright-cli attach my-session playwright-cli -s my-session snapshot"><pre>playwright-cli attach my-session playwright-cli -s my-session snapshot</pre></div> <p><strong>Connect from @playwright/mcp</strong> — or point your MCP server to the running browser.</p> <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="@playwright/mcp --endpoint=my-session"><pre>@playwright/mcp --endpoint=my-session</pre></div> <p><strong>Connect from a Playwright client</strong> — use API to connect to the browser. Multiple clients at a time are supported!</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const browser = await chromium.connect(endpoint);"><pre><span class="pl-k">const</span> <span class="pl-s1">browser</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">chromium</span><span class="pl-kos">.</span><span class="pl-en">connect</span><span class="pl-kos">(</span><span class="pl-s1">endpoint</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div> <p>Pass <code>host</code> and <code>port</code> options to bind over WebSocket instead of a named pipe:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="const { endpoint } = await browser.bind('my-session', { host: 'localhost', port: 0, }); // endpoint is a ws:// URL"><pre><span class="pl-k">const</span> <span class="pl-kos">{</span> endpoint <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">browser</span><span class="pl-kos">.</span><span class="pl-en">bind</span><span class="pl-kos">(</span><span class="pl-s">'my-session'</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">host</span>: <span class="pl-s">'localhost'</span><span class="pl-kos">,</span> <span class="pl-c1">port</span>: <span class="pl-c1">0</span><span class="pl-kos">,</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// endpoint is a ws:// URL</span></pre></div> <p>Call <a href="https://playwright.dev/docs/api/class-browser#browser-unbind" rel="nofollow">browser.unbind()</a> to stop accepting new connections.</p> <h2>📊 Observability</h2> <p>Run <code>playwright-cli show</code> to open the Dashboard that lists all the bound browsers, their statuses, and allows interacting with them:</p> <ul> <li>See what your agent is doing on the background browsers</li> <li>Click into the sessions for manual interventions</li> <li>Open DevTools to inspect pages from the background browsers.</li> </ul> <p><a target="_blank" rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/microsoft/playwright/main/docs/src/images/release-notes-1.59-dashboard.png"><img src="https://raw.githubusercontent.com/microsoft/playwright/main/docs/src/images/release-notes-1.59-dashboard.png" alt="Demo" width="1169" height="835" style="max-width: 100%; height: auto; max-height: 835px;"></a></p> - `playwright-cli` binds all of its browsers automatically, so you can see what your agents are doing. - Pass `PLAYWRIGHT_DASHBOARD=1` env variable to see all `@playwright/test` browsers in the dashboard. <h2>🐛 CLI debugger for agents</h2> <p>Coding agents can now run <code>npx playwright test --debug=cli</code> to attach and debug tests over <code>playwright-cli</code> — perfect for automatically fixing tests in agentic workflows:</p> <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ npx playwright test --debug=cli ### Debugging Instructions - Run "playwright-cli attach tw-87b59e" to attach to this test $ playwright-cli attach tw-87b59e ### Session `tw-87b59e` created, attached to `tw-87b59e`. Run commands with: playwright-cli --session=tw-87b59e <command> ### Paused - Navigate to "/" at output/tests/example.spec.ts:4 $ playwright-cli --session tw-87b59e step-over ### Page - Page URL: https://playwright.dev/ - Page Title: Fast and reliable end-to-end testing for modern web apps | Playwright ### Paused - Expect "toHaveTitle" at output/tests/example.spec.ts:7"><pre>$ npx playwright <span class="pl-c1">test</span> --debug=cli <span class="pl-c"><span class="pl-c">#</span>## Debugging Instructions</span> - Run <span class="pl-s"><span class="pl-pds">"</span>playwright-cli attach tw-87b59e<span class="pl-pds">"</span></span> to attach to this <span class="pl-c1">test</span> $ playwright-cli attach tw-87b59e <span class="pl-c"><span class="pl-c">#</span>## Session `tw-87b59e` created, attached to `tw-87b59e`.</span> Run commands with: playwright-cli --session=tw-87b59e <span class="pl-k"><</span>command<span class="pl-k">></span> <span class="pl-c"><span class="pl-c">#</span>## Paused</span> - Navigate to <span class="pl-s"><span class="pl-pds">"</span>/<span class="pl-pds">"</span></span> at output/tests/example.spec.ts:4 $ playwright-cli --session tw-87b59e step-over <span class="pl-c"><span class="pl-c">#</span>## Page</span> - Page URL: https://playwright.dev/ - Page Title: Fast and reliable end-to-end testing <span class="pl-k">for</span> modern web apps <span class="pl-k">|</span> Playwright <span class="pl-c"><span class="pl-c">#</span>## Paused</span> - Expect <span class="pl-s"><span class="pl-pds">"</span>toHaveTitle<span class="pl-pds">"</span></span> at output/tests/example.spec.ts:7</pre></div> <h2>📋 CLI trace analysis for agents</h2> <p>Coding agents can run <code>npx playwright trace</code> to explore <a href="https://playwright.dev/docs/trace-viewer" rel="nofollow">Playwright Trace</a> and understand failing or flaky tests from the command line:</p> <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ npx playwright trace open test-results/example-has-title-chromium/trace.zip Title: example.spec.ts:3 › has title $ npx playwright trace actions --grep="expect" # Time Action Duration ──── ───────── ─────────────────────────────────────────────────────── ──────── 9. 0:00.859 Expect "toHaveTitle" 5.1s ✗ $ npx playwright trace action 9 Expect "toHaveTitle" Error: expect(page).toHaveTitle(expected) failed Expected pattern: /Wrong Title/ Received string: "Fast and reliable end-to-end testing for modern web apps | Playwright" Timeout: 5000ms Snapshots available: before, after usage: npx playwright trace snapshot 9 --name <before|after> $ npx playwright trace snapshot 9 --name after ### Page - Page Title: Fast and reliable end-to-end testing for modern web apps | Playwright $ npx playwright trace close"><pre>$ npx playwright trace open test-results/example-has-title-chromium/trace.zip Title: example.spec.ts:3 › has title $ npx playwright trace actions --grep=<span class="pl-s"><span class="pl-pds">"</span>expect<span class="pl-pds">"</span></span> <span class="pl-c"><span class="pl-c">#</span> Time Action Duration</span> ──── ───────── ─────────────────────────────────────────────────────── ──────── 9. 0:00.859 Expect <span class="pl-s"><span class="pl-pds">"</span>toHaveTitle<span class="pl-pds">"</span></span> 5.1s ✗ $ npx playwright trace action 9 Expect <span class="pl-s"><span class="pl-pds">"</span>toHaveTitle<span class="pl-pds">"</span></span> Error: expect(page).toHaveTitle(expected) failed Expected pattern: /Wrong Title/ Received string: <span class="pl-s"><span class="pl-pds">"</span>Fast and reliable end-to-end testing for modern web apps | Playwright<span class="pl-pds">"</span></span> Timeout: 5000ms Snapshots available: before, after usage: npx playwright trace snapshot 9 --name <span class="pl-k"><</span>before<span class="pl-k">|</span>after<span class="pl-k">></span> $ npx playwright trace snapshot 9 --name after <span class="pl-c"><span class="pl-c">#</span>## Page</span> - Page Title: Fast and reliable end-to-end testing <span class="pl-k">for</span> modern web apps <span class="pl-k">|</span> Playwright $ npx playwright trace close</pre></div> <h2>♻️ <code>await using</code></h2> <p>Many APIs now return <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncDispose" rel="nofollow">async disposables</a>, enabling the <code>await using</code> syntax for automatic cleanup:</p> <div class="highlight highlight-source-js notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="await using page = await context.newPage(); { await using route = await page.route('**/*', route => route.continue()); await using script = await page.addInitScript('console.log("init script here")'); await page.goto('https://playwright.dev'); // do something } // route and init script have been removed at this point"><pre><span class="pl-k">await</span> <span class="pl-s1">using</span> <span class="pl-s1">page</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">context</span><span class="pl-kos">.</span><span class="pl-en">newPage</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-kos">{</span> <span class="pl-k">await</span> <span class="pl-s1">using</span> <span class="pl-s1">route</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">route</span><span class="pl-kos">(</span><span class="pl-s">'**/*'</span><span class="pl-kos">,</span> <span class="pl-s1">route</span> <span class="pl-c1">=></span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-en">continue</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">using</span> <span class="pl-s1">script</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">addInitScript</span><span class="pl-kos">(</span><span class="pl-s">'console.log("init script here")'</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">goto</span><span class="pl-kos">(</span><span class="pl-s">'https://playwright.dev'</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// do something</span> <span class="pl-kos">}</span> <span class="pl-c">// route and init script have been removed at this point</span></pre></div> <h2>🔍 Snapshots and Locators</h2> <ul> <li>Method <a href="https://playwright.dev/docs/api/class-page#page-aria-snapshot" rel="nofollow">page.ariaSnapshot()</a> to capture the aria snapshot of the page — equivalent to <code>page.locator('body').ariaSnapshot()</code>.</li> <li>Options <code>depth</code> and <code>mode</code> in <a href="https://playwright.dev/docs/api/class-locator#locator-aria-snapshot" rel="nofollow">locator.ariaSnapshot()</a>.</li> <li>Method <a href="https://playwright.dev/docs/api/class-locator#locator-normalize" rel="nofollow">locator.normalize()</a> converts a locator to follow best practices like test ids and aria roles.</li> <li>Method <a href="https://playwright.dev/docs/api/class-page#page-pick-locator" rel="nofollow">page.pickLocator()</a> enters an interactive mode where hovering over elements highlights them and shows the corresponding locator. Click an element to get its <a href="https://playwright.dev/docs/api/class-locator" rel="nofollow">Locator</a> back. Use <a href="https://playwright.dev/docs/api/class-page#page-cancel-pick-locator" rel="nofollow">page.cancelPickLocator()</a> to cancel.</li> </ul> <h2>New APIs</h2> <h3>Screencast</h3> <ul> <li><a href="https://playwright.dev/docs/api/class-page#page-screencast" rel="nofollow">page.screencast</a> provides video recording, real-time frame streaming, and overlay management.</li> <li>Methods <a href="https://playwright.dev/docs/api/class-screencast#screencast-start" rel="nofollow">screencast.start()</a> and <a href="https://playwright.dev/docs/api/class-screencast#screencast-stop" rel="nofollow">screencast.stop()</a> for recording and frame capture.</li> <li>Methods <a href="https://playwright.dev/docs/api/class-screencast#screencast-show-actions" rel="nofollow">screencast.showActions()</a> and <a href="https://playwright.dev/docs/api/class-screencast#screencast-hide-actions" rel="nofollow">screencast.hideActions()</a> for action annotations.</li> <li>Methods <a href="https://playwright.dev/docs/api/class-screencast#screencast-show-chapter" rel="nofollow">screencast.showChapter()</a> and <a href="https://playwright.dev/docs/api/class-screencast#screencast-show-overlay" rel="nofollow">screencast.showOverlay()</a> for visual overlays.</li> <li>Methods <a href="https://playwright.dev/docs/api/class-screencast#screencast-show-overlays" rel="nofollow">screencast.showOverlays()</a> and <a href="https://playwright.dev/docs/api/class-screencast#screencast-hide-overlays" rel="nofollow">screencast.hideOverlays()</a> for overlay visibility control.</li> </ul> <h3>Storage, Console and Errors</h3> <ul> <li>Method <a href="https://playwright.dev/docs/api/class-browsercontext#browser-context-set-storage-state" rel="nofollow">browserContext.setStorageState()</a> clears existing cookies, local storage, and IndexedDB for all origins and sets a new storage state — no need to create a new context.</li> <li>Methods <a href="https://playwright.dev/docs/api/class-page#page-clear-console-messages" rel="nofollow">page.clearConsoleMessages()</a> and <a href="https://playwright.dev/docs/api/class-page#page-clear-page-errors" rel="nofollow">page.clearPageErrors()</a> to clear stored messages and errors.</li> <li>Option <code>filter</code> in <a href="https://playwright.dev/docs/api/class-page#page-console-messages" rel="nofollow">page.consoleMessages()</a> and <a href="https://playwright.dev/docs/api/class-page#page-page-errors" rel="nofollow">page.pageErrors()</a> controls which messages are returned.</li> <li>Method <a href="https://playwright.dev/docs/api/class-consolemessage#console-message-timestamp" rel="nofollow">consoleMessage.timestamp()</a>.</li> </ul> <h3>Miscellaneous</h3> <ul> <li><a href="https://playwright.dev/docs/api/class-browsercontext#browser-context-debugger" rel="nofollow">browserContext.debugger</a> provides programmatic control over the Playwright debugger.</li> <li>Method <a href="https://playwright.dev/docs/api/class-browsercontext#browser-context-is-closed" rel="nofollow">browserContext.isClosed()</a>.</li> <li>Method <a href="https://playwright.dev/docs/api/class-request#request-existing-response" rel="nofollow">request.existingResponse()</a> returns the response without waiting.</li> <li>Method <a href="https://playwright.dev/docs/api/class-response#response-http-version" rel="nofollow">response.httpVersion()</a> returns the HTTP version used by the response.</li> <li>Events <a href="https://playwright.dev/docs/api/class-cdpsession#cdp-session-event-event" rel="nofollow">cdpSession.on('event')</a> and <a href="https://playwright.dev/docs/api/class-cdpsession#cdp-session-event-close" rel="nofollow">cdpSession.on('close')</a> for CDP sessions.</li> <li>Option <code>live</code> in <a href="https://playwright.dev/docs/api/class-tracing#tracing-start" rel="nofollow">tracing.start()</a> for real-time trace updates.</li> <li>Option <code>artifactsDir</code> in <a href="https://playwright.dev/docs/api/class-browsertype#browser-type-launch" rel="nofollow">browserType.launch()</a> to configure the artifacts directory.</li> </ul> <h2>🛠️ Other improvements</h2> <ul> <li>UI Mode has an option to only show tests affected by source changes.</li> <li>UI Mode and Trace Viewer have improved action filtering.</li> <li>HTML Reporter shows the list of runs from the same worker.</li> <li>HTML Reporter allows filtering test steps for quick search.</li> <li>New trace mode <code>'retain-on-failure-and-retries'</code> records a trace for each test run and retains all traces when an attempt fails — great for comparing a passing trace with a failing one from a flaky test.</li> </ul> <h2>Known Issues <g-emoji class="g-emoji" alias="warning">⚠️</g-emoji><g-emoji class="g-emoji" alias="warning">⚠️</g-emoji></h2> <ul> <li><code>navigator.platform</code> emulation can cause Ctrl or Meta dispatching errors (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="4188854024" data-permission-text="Title is private" data-url="https://github.com/microsoft/playwright/issues/40009" data-hovercard-type="issue" data-hovercard-url="/microsoft/playwright/issues/40009/hovercard" href="https://github.com/microsoft/playwright/issues/40009">#40009</a>). Pass <code>PLAYWRIGHT_NO_UA_PLATFORM = '1'</code> environment variable while we are issuing a patch release. Let us know in the issue how it affected you.</li> </ul> <h2>Breaking Changes <g-emoji class="g-emoji" alias="warning">⚠️</g-emoji></h2> <ul> <li>Removed macOS 14 support for WebKit. We recommend upgrading your macOS version, or keeping an older Playwright version.</li> <li>Removed <code>@playwright/experimental-ct-svelte</code> package.</li> <li><code>junit</code> test reporter now differentiates between types of errors, so some of the previous <code><failure></code>s are now reported as <code><error></code>s.</li> </ul> <h2>Browser Versions</h2> <ul> <li>Chromium 147.0.7727.15</li> <li>Mozilla Firefox 148.0.2</li> <li>WebKit 26.4</li> </ul> <p>This version was also tested against the following stable channels:</p> <ul> <li>Google Chrome 146</li> <li>Microsoft Edge 146</li> </ul>

Cybersecurity · Krebs on Security

Alleged Kimwolf Botmaster ‘Dort’ Arrested, Charged in U.S. and Canada

Canadian authorities on Wednesday arrested a 23-year-old Ottawa man on suspicion of building and operating Kimwolf, a fast spreading Internet-of-Things botnet that enslaved millions of devices for use in a series of massive distributed denial-of-service (DDoS) attacks over the past six months. KrebsOnSecurity publicly named the suspect in February 2026 after the accused launched a volley of DDoS, doxing and swatting campaigns against this author and a security researcher. He now faces criminal hacking charges in both Canada and the United States.

Developers & Open Source · Mozilla Hacks

Announcing Web Serial Support in Firefox

Support for Web Serial in Firefox 151 for Desktop Firefox can now connect directly to microcontrollers, development boards, 3D printers, power meters, and other serial-connected hardware from the web. Starting in Firefox 151 for Desktop, support for the Web Serial API allows web applications to communicate with compatible devices without requiring native software. Web Serial […] The post Announcing Web Serial Support in Firefox appeared first on Mozilla Hacks - the Web developer blog .

Products & Consumer Tech · Product Hunt

Browse.sh

Give your agents muscle memory for automating the web Discussion | Link

Products & Consumer Tech · Product Hunt

Publora

The publishing API for the agent era Discussion | Link

Developers & Open Source · Node.js Blog

Node.js 24.16.0 (LTS)

Node.js Blog published: Node.js 24.16.0 (LTS)

Products & Consumer Tech · Product Hunt

Mentic

Autonomous AI agent that runs your Meta ads end-to-end Discussion | Link

AI · Google AI Blog

We’re announcing new community investments in Missouri.

<img src="https://storage.googleapis.com/gweb-uniblog-publish-prod/images/MissouriSocial.max-600x600.format-webp.webp">We’re helping build the state’s next-generation workforce and investing in energy programs.

AI · Google AI Blog

100 things we announced at I/O 2026

<img src="https://storage.googleapis.com/gweb-uniblog-publish-prod/images/100_things_Social.max-600x600.format-webp.webp">We've been busy! Here’s a rundown of the top announcements, launches and demos at I/O 2026.

Cloud & Infrastructure · Azure Blog

Azure IaaS: Deploy high-performance workloads with a system-level approach

Performance in the cloud is no longer defined by individual resources—it’s shaped by how compute, storage, and networking work together. Azure IaaS takes a system-level approach to help organizations achieve consistent, scalable performance across AI, cloud-native, and business-critical workloads. The post Azure IaaS: Deploy high-performance workloads with a system-level approach appeared first on Microsoft Azure Blog . ]]>

Developers & Open Source · Node.js Blog

Node.js 26.2.0 (Current)

Node.js Blog published: Node.js 26.2.0 (Current)

Cloud & Infrastructure · Kubernetes Blog

Announcing etcd 3.7.0-beta.0

SIG-Etcd announces the availability of the first beta release of etcd v3.7.0 . This new version of the popular distributed database and key Kubernetes component includes the long-requested RangeStream feature, as well as a refactoring and cleanup of multiple legacy components and interfaces. v3.7 will deliver improved security, better operational reliability, and an improved experience for working with large resultsets. First, however, the project needs users to test the beta. You can find v3.7.0-beta.0 here: Source code Binaries Official container images Please try it out and report issues in the etcd repo . This beta also determines the EOL of version 3.4. RangeStream In etcd v3.6 and earlier, it is challenging to work with requests that return large resultsets. The client or requesting application is forced to wait for the full result set, leading to unpredictable latency and memory usage. The RangeStream RPC lets calling applications accept result sets in chunks, reducing latency and making buffering memory usage more predictable. Much of the work on RangeStream was done by a relatively new contributor to etcd, Jeffrey Ying , a software engineer at Google. New contributors can have a substantial impact on etcd development. "I've always been fascinated by database internals, and building RangeStream was a great opportunity to solve a bottleneck we were hitting in production with Kubernetes. It was the perfect opportunity to collaborate across projects and improve the ecosystem as a whole. Jumping into etcd as a new contributor had a bit of a learning curve, but the community is incredibly welcoming. The leads were very receptive to my ideas and helped me iterate quickly, while maintaining the project's high bar for reliability and code quality," said Jeffrey. Instructions on how to use RangeStream in gRPC calls and in etcdctl can be found in the etcd documentation. Users should try it out for their own applications. Removal of v2store The last vestiges of etcd v2store have been removed in v3.7, making this the first release that is 100% on v3store. This includes discovery , bootstrap , v2 requests , and the v2 client . Our team has also removed multiple deprecated experimental flags . All of these changes may create some breakage for users, particularly those who have not already updated to v3.6.11. We are interested in hearing about blockers encountered by users and dependent applications; please report anything you find that can't be remedied or needs better upgrade documentation. etcd v3.7.0-beta.0 also includes bbolt v1.5.0 and raft v3.7.0 . 3.4 EOL According to our community support policy , we typically maintain only the latest two minor versions, currently v3.6 and v3.5. Etcd v3.5 will be supported for 1 year after v3.7.0 final release. As mentioned in extended support for v3.4 in the etcd v3.6.0 release announcement, etcd v3.4 has been EOL since May 15, 2026. SIG-etcd may release one more security patch for that version at the end of May, if warranted by patched vulnerabilities. In any case, it will cease being updated after the end of May. Users on v3.4 should be planning to upgrade their clusters . Feedback and Future Betas Reach the etcd contributors with your feedback about v3.7.0-beta.0 in any of the following places: Github issues #SIG-etcd slack channel in Kubernetes Slack etcd-dev mailing list SIG-etcd may release additional betas of version v3.7.0 with additional refactoring, particularly of our use of protobuf libraries. Release candidates and the final release will probably happen through June, possibly into early July.

Products & Consumer Tech · Product Hunt

prostir zvuku

A spatial nature sound mixer for Mac Discussion | Link

Products & Consumer Tech · Product Hunt

Upstream

The inbox designed for humans and agents Discussion | Link

Cybersecurity · Krebs on Security

CISA Admin Leaked AWS GovCloud Keys on Github

Until this past weekend, a contractor for the Cybersecurity & Infrastructure Security Agency (CISA) maintained a public GitHub repository that exposed credentials to several highly privileged AWS GovCloud accounts and a large number of internal CISA systems. Security experts said the public archive included files detailing how CISA builds, tests and deploys software internally, and that it represents one of the most egregious government data leaks in recent history.

Cloud & Infrastructure · AWS News Blog

AWS Weekly Roundup: AWS Transform at 1 year, Claude Platform on AWS, EC2 M3 Ultra Mac instances, and more (May 18, 2026)

Just a year ago, we launched AWS Transform for .NET, Mainframe and VMware workloads, the first agentic AI service purpose-built for modernizing enterprise applications at scale. At re:Invent 2025, we introduced AWS Transform custom, which enables organizations to modernize and transform code at scale using AWS-managed and custom transformations. You can upgrade language versions, migrate […]

Products & Consumer Tech · Product Hunt

Buply

Reunite lost pets using noseprints Discussion | Link

Developers & Open Source · The Changelog

MCP on Code Mode

This week I'm talking with Matt Carey about Code Mode and how most of us have been thinking about MCP all wrong. Matt works on the Agents SDK and MCP at Cloudflare — we discuss how server-side Code Mode lets one MCP server expose all ~2,500 Cloudflare API endpoints in about 1,000 tokens of context, the dynamic Worker loader that runs model-written code safely in a V8 isolate, Matt's own workflow with Claude, where memory fits into the future of agents, and his Zaggy git wrapper that keeps agents from force-pushing his repos.

Cloud & Infrastructure · Kubernetes Blog

Kubernetes v1.36: New Metric for Route Sync in the Cloud Controller Manager

This article was originally published with the wrong date. It was later republished, dated the 15th of May 2026. Kubernetes v1.36 introduces a new alpha counter metric route_controller_route_sync_total to the Cloud Controller Manager (CCM) route controller implementation at k8s.io/cloud-provider . This metric increments each time routes are synced with the cloud provider. A/B testing watch-based route reconciliation This metric was added to help operators validate the CloudControllerManagerWatchBasedRoutesReconciliation feature gate introduced in Kubernetes v1.35 . That feature gate switches the route controller from a fixed-interval loop to a watch-based approach that only reconciles when nodes actually change. This reduces unnecessary API calls to the infrastructure provider, lowering pressure on rate-limited APIs and allowing operators to make more efficient use of their available quota. To A/B test this, compare route_controller_route_sync_total with the feature gate disabled (default) versus enabled. In clusters where node changes are infrequent, you should see a significant drop in the sync rate with the feature gate turned on. Example: expected behavior With the feature gate disabled (the default fixed-interval loop), the counter increments steadily regardless of whether any node changes occurred: # After 10 minutes with no node changes route_controller_route_sync_total 60 # After 20 minutes, still no node changes route_controller_route_sync_total 120 With the feature gate enabled (watch-based reconciliation), the counter only increments when nodes are actually added, removed, or updated: # After 10 minutes with no node changes route_controller_route_sync_total 1 # After 20 minutes, still no node changes — counter unchanged route_controller_route_sync_total 1 # A new node joins the cluster — counter increments route_controller_route_sync_total 2 The difference is especially visible in stable clusters where nodes rarely change. Where can I give feedback? If you have feedback, feel free to reach out through any of the following channels: The #sig-cloud-provider channel on Kubernetes Slack The KEP-5237 issue on GitHub The SIG Cloud Provider community page for other communication channels How can I learn more? For more details, refer to KEP-5237 .

Cloud & Infrastructure · Kubernetes Blog

Kubernetes v1.36: Mixed Version Proxy Graduates to Beta

Back in Kubernetes 1.28, we introduced the Mixed Version Proxy (MVP) as an Alpha feature (under the feature gate UnknownVersionInteroperabilityProxy ) in a previous blog post . The goal was simple but critical: make cluster upgrades safer by ensuring that requests for resources not yet known to an older API server are correctly routed to a newer peer API server, instead of returning an incorrect 404 Not Found . We are excited to announce that the Mixed Version Proxy is moving to Beta in Kubernetes 1.36 and will be enabled by default! The feature has evolved significantly since its initial release, addressing key gaps and modernizing its architecture. Here is a look at how the feature has evolved and what you need to know to leverage it in your clusters. What problem are we solving? In a highly available control plane undergoing an upgrade, you often have API servers running different versions. These servers might serve different sets of APIs (Groups, Versions, Resources). Without MVP, if a client request lands on an API server that does not serve the requested resource (e.g., a new API version introduced in the upgrade), that server returns a 404 Not Found . This is technically incorrect because the resource is available in the cluster, just not on that specific server. This can lead to serious side effects, such as mistaken garbage collection or blocked namespace deletions. MVP solves this by proxying the request to a peer API server that can serve it. sequenceDiagram participant Client participant API_Server_A as API Server A (Older/Different) participant API_Server_B as API Server B (Newer/Capable) Client->>API_Server_A: 1. Request for Resource (e.g., v2) Note over API_Server_A: Determines it cannot serve locally API_Server_A->>API_Server_A: 2. Looks up capable peer in Discovery Cache API_Server_A->>API_Server_B: 3. Proxies request (adds x-kubernetes-peer-proxied header) API_Server_B->>API_Server_B: 4. Processes request locally API_Server_B-->>API_Server_A: 5. Returns Response API_Server_A-->>Client: 6. Forwards Response How has it evolved since 1.28 The initial Alpha implementation was a great proof of concept, but it had some limitations and relied on older mechanisms. Here is how we have modernized it for Beta: From StorageVersion API to Aggregated Discovery In the Alpha version, API servers relied on the StorageVersion API to figure out which peers served which resources. While functional, this approach had a significant limitation: the StorageVersion API is not yet supported for CRDs and aggregated APIs. For Beta, we have replaced the reliance on StorageVersion API calls with the use of Aggregated Discovery . API servers now use the aggregated discovery data to dynamically understand the capabilities of their peers. The Missing Piece: Peer-Aggregated Discovery The 1.28 blog post noted a significant gap: while we could proxy resource requests, discovery requests still only showed what the local API server knew about. In 1.36, we have added Peer-Aggregated Discovery support! Now, when a client performs discovery (e.g., listing available APIs), the API server merges its local view with the discovery data from all active peers. This provides clients with a complete, unified view of all APIs available across the entire cluster, regardless of which API server they connected to. sequenceDiagram participant Client participant API_Server_A as API Server A participant API_Server_B as API Server B Client->>API_Server_A: 1. Request Discovery Document API_Server_A->>API_Server_A: 2. Gets Local APIs API_Server_A->>API_Server_B: 3. Gets Peer APIs (Cached or Direct) API_Server_A->>API_Server_A: 4. Merges and sorts lists deterministically API_Server_A-->>Client: 5. Returns Unified Discovery Document While peer-aggregated discovery will be the default behavior (note that peer-aggregated discovery is enabled if the --peer-ca-file flag is set, otherwise the server will fallback to showing only its local APIs), there may be cases where you need to inspect only the resources served by the specific API server you are connected to. You can request this non-aggregated view by including the profile=nopeer parameter in your request's Accept header (e.g., Accept: application/json;g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList;profile=nopeer ). Required configuration While the feature gate will be enabled by default, it requires certain flags to be set to allow for secure communication between peer API servers. To function correctly, make sure your API server is configured with the following flags: --feature-gates=UnknownVersionInteroperabilityProxy=true : This will be default in 1.36, but it is good to verify --peer-ca-file= : [CRITICAL] This is a required flag. You must provide the CA bundle that the source API server will use to authenticate the serving certificates of destination peer API servers. Without this, proxying will fail due to TLS verification errors. --peer-advertise-ip and --peer-advertise-port : These flags are used to set the network address that peers should use to reach this API server. If unset, the values from --advertise-address or --bind-address are used. If you have complex network topologies where API servers communicate over a specific internal interface, setting these flags explicitly is highly recommended. Configuring with kubeadm If you manage your cluster with kubeadm , you can configure these flags in your ClusterConfiguration file: apiVersion : kubeadm.k8s.io/v1beta4 kind : ClusterConfiguration apiServer : extraArgs : peer-ca-file : "/etc/kubernetes/pki/ca.crt" # peer-advertise-ip and port if needed Call to action If you are running multi-master clusters and upgrading them regularly, the Mixed Version Proxy is a major safety improvement. With it becoming default in 1.36, we encourage you to: Review your API server flags to ensure --peer-ca-file is set properly. Test the feature in your staging environments as you prepare for the 1.36 upgrade. Provide feedback to SIG API Machinery ( Slack , mailing list , or by attending SIG API Machinery meetings ) on your experience.

Products & Consumer Tech · Product Hunt

Overly

Search and ask questions inside lecture videos Discussion | Link

Cloud & Infrastructure · AWS News Blog

Amazon Bedrock introduces new advanced prompt optimization and migration tool

Amazon Bedrock Advanced Prompt Optimization enables customers to optimize their prompts for their current model or migrate prompts to new models faster than before with built-in evaluation feedback loops. Optimize your prompts and compare results for up to 5 models simultaneously.

Cloud & Infrastructure · Kubernetes Blog

Kubernetes v1.36: Deprecation and removal of Service ExternalIPs

<p>The <code>.spec.externalIPs</code> field for <a href="https://kubernetes.io/docs/concepts/services-networking/service/">Service</a> was an early attempt to provide cloud-load-balancer-like functionality for non-cloud clusters. Unfortunately, the API assumes that every user in the cluster is fully trusted, and in any situation where that is not the case, it enables various security exploits, as described in <a href="https://www.cvedetails.com/cve/CVE-2020-8554/">CVE-2020-8554</a>.</p> <p>Since Kubernetes 1.21, the Kubernetes project has recommended that all users disable <code>.spec.externalIPs</code>. To make that easier, Kubernetes also added an admission controller (<code>DenyServiceExternalIPs</code>) that can be enabled to do this. At the time, SIG Network felt that blocking the functionality by default was too large a breaking change to consider.</p> <p>However, the security problems are still there, and as a project we're increasingly unhappy with the "insecure by default" state of the feature. Additionally, there are now several better alternatives for non-cloud clusters wanting load-balancer-like functionality.</p> <p>As a result, the <code>.spec.externalIPs</code> field for Service is now formally deprecated in Kubernetes 1.36. We expect that a future minor release of Kubernetes will drop implementation of the behavior from <code>kube-proxy</code>, and will update the Kubernetes <a href="https://www.cncf.io/training/certification/software-conformance/">conformance</a> criteria to require that conforming implementations <strong>do not</strong> provide support.</p> <h2 id="terminology">A note on terminology, and what hasn't been deprecated<a class="td-heading-self-link" href="#terminology" aria-label="Heading self-link"></a></h2><p>The phrase <em>external IP</em> is somewhat overloaded in Kubernetes:</p> <ul> <li> <p>The Service API has a field <code>.spec.externalIPs</code> that can be used to add additional IP addresses that a Service will respond on.</p> </li> <li> <p>The Node API's <code>.status.addresses</code> field can list addresses of several different types, one of which is called <code>ExternalIP</code>.</p> </li> <li> <p>The <code>kubectl</code> tool, when displaying information about a Service of type LoadBalancer in the default output format, will show the load balancer IP address under the column heading <code>EXTERNAL-IP</code>.</p> </li> </ul> <p>This deprecation is about the first of those. If you are not setting the field <code>externalIPs</code> in any of your Services, then it does not apply to you.</p> <p>That said, as a precaution, you may still want to enable the <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#denyserviceexternalips">DenyServiceExternalIPs</a> admission controller to block any future use of the <code>externalIPs</code> field.</p> <h2 id="alternatives">Alternatives to <code>externalIPs</code><a class="td-heading-self-link" href="#alternatives" aria-label="Heading self-link"></a></h2><p>If you are using <code>.spec.externalIPs</code>, then there are several alternatives.</p> <p>Consider a Service like the following:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-example-service</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">selector</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">app.kubernetes.io/name</span><span class="p">:</span><span class="w"> </span><span class="l">my-example-app</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">externalIPs</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="s2">"192.0.2.4"</span><span class="w"> </span></span></span></code></pre></div><h3 id="alternative-LoadBalancer">Using manually-managed LoadBalancer Services instead of <code>externalIPs</code><a class="td-heading-self-link" href="#alternative-LoadBalancer" aria-label="Heading self-link"></a></h3><p>The easiest (but also worst) option is to just switch from using <code>externalIPs</code> to using a <code>type: LoadBalancer</code> service, and assigning a load balancer IP by hand. This is, essentially, exactly the same as <code>externalIPs</code>, with one important difference: the load balancer IP is part of the Service's <code>.status</code>, not its <code>.spec</code>, and in a cluster with RBAC enabled, it can't be edited by ordinary users by default. Thus, this replacement for <code>externalIPs</code> would only be available to users who were given permission by the admins (although those users would then be fully empowered to replicate CVE-2020-8554; there would still not be any further checks to ensure that one user wasn't stealing another user's IPs, etc.)</p> <p>Because of the way that <code>.status</code> works in Kubernetes, you must create the Service without a load balancer IP, and then add the IP as a second step:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> cat loadbalancer-service.yaml </span></span><span class="line"><span class="cl"><span class="go">apiVersion: v1 </span></span></span><span class="line"><span class="cl"><span class="go">kind: Service </span></span></span><span class="line"><span class="cl"><span class="go">metadata: </span></span></span><span class="line"><span class="cl"><span class="go"> name: my-example-service </span></span></span><span class="line"><span class="cl"><span class="go">spec: </span></span></span><span class="line"><span class="cl"><span class="go"> # prevent any real load balancer controllers from managing this service </span></span></span><span class="line"><span class="cl"><span class="go"> # by using a non-existent loadBalancerClass </span></span></span><span class="line"><span class="cl"><span class="go"> loadBalancerClass: non-existent-class </span></span></span><span class="line"><span class="cl"><span class="go"> type: LoadBalancer </span></span></span><span class="line"><span class="cl"><span class="go"> selector: </span></span></span><span class="line"><span class="cl"><span class="go"> app.kubernetes.io/name: my-example-app </span></span></span><span class="line"><span class="cl"><span class="go"> ports: </span></span></span><span class="line"><span class="cl"><span class="go"> - protocol: TCP </span></span></span><span class="line"><span class="cl"><span class="go"> port: 80 </span></span></span><span class="line"><span class="cl"><span class="go"> targetPort: 8080 </span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="gp">$</span> kubectl apply -f loadbalancer-service.yaml </span></span><span class="line"><span class="cl"><span class="go">service/my-example-service created </span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="gp">$</span> kubectl patch service my-example-service --subresource<span class="o">=</span>status --type<span class="o">=</span>merge -p <span class="s1">'{"status":{"loadBalancer":{"ingress":[{"ip":"192.0.2.4"}]}}}'</span> </span></span></code></pre></div><h3 id="alternative-load-balancer-controller">Using a non-cloud based load balancer controller<a class="td-heading-self-link" href="#alternative-load-balancer-controller" aria-label="Heading self-link"></a></h3><p>Although <code>LoadBalancer</code> services were originally designed to be backed by cloud load balancers, Kubernetes can also support them on non-cloud platforms by using a third-party load balancer controller such as <a href="https://metallb.io/">MetalLB</a>. This solves the security problems associated with <code>externalIPs</code> because the administrator can configure what ranges of IP addresses the controller will assign to services, and the controller will ensure that two services can't both use the same IP.</p> <p>So, for example, after <a href="https://metallb.io/installation/">installing</a> and <a href="https://metallb.io/configuration/">configuring</a> MetalLB, a cluster administrator could configure a pool of IP addresses for use in the cluster:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">metallb.io/v1beta1</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">IPAddressPool</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">metallb-system</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">addresses</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">192.0.2.0</span><span class="l">/24</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">autoAssign</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">avoidBuggyIPs</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span></span></span></code></pre></div><p>After which a user can create a <code>type: LoadBalancer</code> Service and MetalLB will handle the assignment of the IP address. MetalLB even supports the deprecated <code>loadBalancerIP</code> field in Service, so the end user can request a specific IP (assuming it is available) for backward-compatibility with the <code>externalIPs</code> approach, rather than being assigned one at random:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">my-example-service</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">LoadBalancer</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">selector</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">app.kubernetes.io/name</span><span class="p">:</span><span class="w"> </span><span class="l">my-example-app</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">loadBalancerIP</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.0.2.4"</span><span class="w"> </span></span></span></code></pre></div><p>Similar approaches would work with other load balancer controllers. This approach can allow cluster administrators to have control over which IP addresses are assigned, rather than users.</p> <h3 id="alternative-gateway-api">Using Gateway API<a class="td-heading-self-link" href="#alternative-gateway-api" aria-label="Heading self-link"></a></h3><p>Another potential solution is to use an implementation of the <a href="https://gateway-api.sigs.k8s.io/">Gateway API</a>.</p> <p>Gateway API allows cluster administrators to define a Gateway resource, which can have an IP address attached to it via the <code>.spec.addresses</code> field. Since Gateway resources are designed to be managed by <a href="https://gateway-api.sigs.k8s.io/concepts/security/">cluster administrators</a>, RBAC rules can be put in place to only allow privileged users to manage them.</p> <p>An example of how this could look is:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">gateway.networking.k8s.io/v1</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Gateway</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example-gateway</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">gatewayClassName</span><span class="p">:</span><span class="w"> </span><span class="l">example-gateway-class</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">addresses</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">IPAddress</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.0.2.4"</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">gateway.networking.k8s.io/v1</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">HTTPRoute</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example-route</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">parentRefs</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example-gateway</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">rules</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">backendRefs</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example-svc</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example-svc</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">selector</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">app.kubernetes.io/name</span><span class="p">:</span><span class="w"> </span><span class="l">example-app</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w"> </span></span></span></code></pre></div><p>The Gateway API project is the next generation of Kubernetes Ingress, Load Balancing, and Service Mesh APIs within Kubernetes. Gateway API was designed to fix the shortcomings of the Service and Ingress resource, making it a very reliable robust solution that is under active development.</p> <h2 id="timeline-for-externalips-deprecation">Timeline for <code>externalIPs</code> deprecation<a class="td-heading-self-link" href="#timeline-for-externalips-deprecation" aria-label="Heading self-link"></a></h2><p>The rough timeline for this deprecation is as follows:</p> <ol> <li>With the release of Kubernetes 1.36, the field was deprecated; Kubernetes now emits <a href="https://kubernetes.io/blog/2020/09/03/warnings/">warnings</a> when a user uses this field</li> <li>About a year later (v1.40 at the earliest) support for <code>.spec.externalIPs</code> will be disabled in kube-proxy, but users will have a way to opt back in should they require more time to migrate away</li> <li>About another year later - (v1.43 at the earliest) support will be disabled completely; users won't have a way to opt back in</li> </ol>

Developers & Open Source · The Changelog

Automation at the speed of Swamp

This week I'm talking with Adam Jacob, founder of System Initiative and creator of Swamp, about what happens when AI agents change the entire shape of software development. We discuss how he went from an 18-person team down to five and shipped Swamp 900 times in four weeks, why he brought User Acceptance Testing (UAT) testing back from the 90s, why software architecture (and domain-driven design) suddenly matters more than knowing how to write code, the live demo where I pointed Swamp at my Proxmox box and watched it write its own automation (blew my mind!!), and why he'll never accept a pull request to Swamp, ever.