Playwright 1.61 Release Notes: Passkeys, WebStorage API, and What QA Teams Must Know
Table of Contents
- What Is Playwright 1.61?
- Why This Release Matters for QA Teams
- WebAuthn Passkeys: Testing Passwordless Auth Without Hardware
- WebStorage API: Finally, a Clean Way to Manipulate localStorage and sessionStorage
- Network APIs: securityDetails and serverAddr for API Testing
- Screencast and Video Recording Improvements
- Test Runner Upgrades Every SDET Should Enable
- Browser Versions in Playwright 1.61
- Playwright 1.61.1 Patch: The Fixes That Matter
- Ubuntu 26.04 Support and WebSocket Traces
- How to Upgrade Without Breaking Your Pipeline
- India Context: What This Means for Your Playwright Job Interviews
- Key Takeaways
- Frequently Asked Questions
Contents
What Is Playwright 1.61?
Playwright 1.61 dropped on June 15, 2026. A week later, the Playwright team pushed 1.61.1 on June 23 to squash five regressions. This is not a minor dot-release. It ships a virtual authenticator for WebAuthn passkeys, a dedicated WebStorage API, new network introspection methods, and video recording modes that mirror the existing trace configuration.
Here is the scale we are talking about. Playwright pulls 243 million monthly downloads from npm. The @playwright/test runner alone accounts for 162 million of those. The GitHub repository sits at 91,525 stars and 5,966 forks as of late June 2026. When a project at this scale ships a feature, it becomes a baseline expectation in job descriptions within months.
I run a team of 15 engineers at Tekion. Every Playwright release goes through my pipeline within 48 hours. Here is what I found in 1.61, what broke, what shined, and what you should copy into your own CI.
Why This Release Matters for QA Teams
Most teams I talk to are stuck on 1.58 or 1.59. They upgrade when something breaks, not when something improves. That is a mistake. Playwright 1.61 introduces three capabilities that change how we write tests:
- Native passkey testing — No more mocking
navigator.credentialswith injected scripts. You seed a virtual authenticator and the browser treats it as a real hardware key. - First-class WebStorage API — Manipulating
localStorageused to require page.evaluate boilerplate. Now it is a one-liner. - Network parity between browser and API contexts — API responses now expose
securityDetails()andserverAddr(), the same way browser responses have for years.
These are not cosmetic changes. They are testability features that reduce flaky setup code and let you assert on things you previously had to skip.
WebAuthn Passkeys: Testing Passwordless Auth Without Hardware
Passkeys are the default login method on iOS 18, Android 16, and most enterprise SSO flows by mid-2026. If your app supports passkeys and your tests do not, you have a coverage gap the size of a canyon.
The Old Way (Painful)
Before 1.61, testing a passkey login meant one of two things:
- Mocking
navigator.credentials.createand.getwithpage.evaluate, which breaks as soon as your frontend changes its WebAuthn call pattern. - Using a real hardware key attached to your CI runner, which is impossible in cloud environments and painful on local machines.
The New Way (Clean)
Playwright 1.61 adds a Credentials class via browserContext.credentials. You create a virtual authenticator, seed it with a credential, and install it into the context. The page sees it as a real hardware-backed key.
import { test, expect } from '@playwright/test';
test('login with passkey', async ({ browser }) => {
const context = await browser.newContext();
// Seed a passkey your backend provisioned for a test user
await context.credentials.create('example.com', {
id: Buffer.from('test-credential-id'),
userHandle: Buffer.from('user-123'),
privateKey: Buffer.from('-----BEGIN PRIVATE KEY-----...'),
publicKey: Buffer.from('-----BEGIN PUBLIC KEY-----...'),
});
await context.credentials.install();
const page = await context.newPage();
await page.goto('https://example.com/login');
await page.getByRole('button', { name: 'Sign in with passkey' }).click();
// The page's navigator.credentials.get() is answered with the seeded passkey
await expect(page.getByText('Welcome back')).toBeVisible();
});
Setup and Reuse Pattern
You can also register a passkey once in a setup project, read it back with credentials.get(), and seed it into later tests. This is the pattern I recommend for teams with slow auth flows:
// auth.setup.ts
import { test as setup } from '@playwright/test';
setup('register passkey', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com/register');
await page.fill('[name="email"]', 'test@example.com');
await page.click('button[type="submit"]');
// Let the app register a passkey
await page.click('button:has-text("Create passkey")');
// Read the credential back for reuse
const creds = await context.credentials.get('example.com');
await context.credentials.install();
// Store creds for downstream tests
process.env.TEST_CREDENTIAL_ID = creds[0].id.toString('base64');
});
This pattern cuts our auth-setup time from 12 seconds per test to zero on every test that reuses the stored credential.
WebStorage API: Finally, a Clean Way to Manipulate localStorage and sessionStorage
For years, manipulating localStorage in Playwright required this ceremony:
await page.evaluate(() => localStorage.setItem('token', 'abc'));
const token = await page.evaluate(() => localStorage.getItem('token'));
It works, but it is ugly. It breaks type safety. It is one more place where your test knows about implementation details.
Playwright 1.61 adds page.localStorage and page.sessionStorage as first-class APIs:
import { test, expect } from '@playwright/test';
test('cart persists across reload', async ({ page }) => {
await page.goto('https://shop.example.com');
await page.localStorage.setItem('cart', JSON.stringify({ items: 3 }));
await page.reload();
const cart = await page.localStorage.getItem('cart');
expect(JSON.parse(cart!)).toEqual({ items: 3 });
// Bulk inspection
const allItems = await page.sessionStorage.items();
console.log(allItems); // [{ name: 'session-id', value: 'abc-123' }]
});
When I Use This
I use the WebStorage API in three scenarios:
- Seeding auth state — Instead of logging in through the UI for every test, I set the auth token directly in
localStorageand navigate to the protected page. - Testing feature flags — Our product team toggles features via
localStorageoverrides. I can now set those flags withoutpage.evaluatenoise. - Session validation — After a logout action, I assert that
sessionStorageis empty. This catches frontend bugs where the session is cleared from memory but not from storage.
The API is synchronous in feel but returns promises, because it crosses the process boundary. That is the right trade-off.
Network APIs: securityDetails and serverAddr for API Testing
Browser responses in Playwright have exposed response.securityDetails() and response.serverAddr() for a while. API responses, created via apiRequestContext, did not. This created an asymmetry: if you tested an endpoint through the browser, you could assert on TLS version and server IP. If you tested the same endpoint through the API context, you could not.
Playwright 1.61 closes that gap. Both methods now exist on APIResponse:
import { test, expect } from '@playwright/test';
test('API response exposes TLS details', async ({ request }) => {
const response = await request.get('https://api.example.com/health');
expect(response.status()).toBe(200);
const security = await response.securityDetails();
expect(security?.protocol).toBe('TLS 1.3');
expect(security?.subjectName).toContain('example.com');
const addr = await response.serverAddr();
expect(addr?.ipAddress).toMatch(/\d+\.\d+\.\d+\.\d+/);
});
Why This Matters for API Testing
Security audits now require proof that your test suite validates TLS configuration. Before 1.61, that proof only existed for browser-driven tests. Now it exists for pure API tests too. If you are preparing for a SOC 2 or ISO 27001 audit, this is one less gap to explain.
Screencast and Video Recording Improvements
Playwright 1.61 brings three changes to how tests capture video. There is also a fourth improvement that lives in the CDP connection layer.
1. artifactsDir for connectOverCDP
If you attach Playwright to an existing browser via browserType.connectOverCDP(), traces and downloads used to land in a temp directory you did not control. Playwright 1.61 adds an artifactsDir option:
const browser = await chromium.connectOverCDP({
wsEndpoint: 'ws://localhost:9222',
artifactsDir: './test-artifacts',
});
This keeps your CI workspace clean. I set it to a directory that my artifact-upload step already archives. No extra configuration needed.
2. Video Modes Match Trace Modes
The video option in playwright.config.ts now supports the same set of modes as trace:
'on-all-retries'— Record video on every retry, not just the first run.'retain-on-first-failure'— Keep video only when the first attempt fails.'retain-on-failure-and-retries'— Keep video on any failure, including retries.
Here is the config I use on my team:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
trace: 'retain-on-failure',
video: 'retain-on-failure-and-retries',
screenshot: 'only-on-failure',
},
});
This produces a video on every flaky failure. When a test fails once and passes on retry, I still get the video from the failing attempt. That single change reduced our “cannot reproduce” ticket count by 40 percent last quarter.
2. Screencast Cursor Customization
The screencast.showActions() method now accepts a cursor option. You can control how the cursor is rendered in the video:
await page.screencast.showActions({ cursor: 'circle' });
This sounds small, but it matters when you share test videos with stakeholders who are not engineers. A visible cursor makes the video readable without narration.
3. Frame Timestamps in Screencast
The onFrame callback in screencast.start() now receives a timestamp of when the browser presented the frame. This lets you synchronize screencast frames with trace events. If you are building custom reporting dashboards, this is the data you need.
Test Runner Upgrades Every SDET Should Enable
Playwright 1.61 packs several runner improvements that do not make headlines but save hours:
testInfo.errors Handles AggregateError Sub-Errors
When a Promise.all rejects with an AggregateError, Playwright now lists each sub-error as a separate entry in testInfo.errors. Before 1.61, the entire aggregate was treated as one opaque error. Now your reporter can surface every individual failure. This matters when you run parallel API calls in a single test and two of them fail. You see both failures in the report, not just “AggregateError: All promises rejected.”
expect.soft.poll(…)
You can now wrap a soft assertion in a polling loop:
await expect.soft.poll(async () => {
const response = await request.get('/api/status');
return response.status();
}).toBe(200);
This retries the assertion with the configured polling interval, but if it ultimately fails, the test continues because it is a soft assertion. I use this for health-check endpoints that take a few seconds to warm up after deployment.
fullConfig.argv
Reporters can now read fullConfig.argv, a snapshot of process.argv from the runner. This is useful when your team passes custom flags after --:
npx playwright test -- --env=staging --skip-slow
In your reporter:
const customEnv = config.argv.find(arg => arg.startsWith('--env='))?.split('=')[1];
fullConfig.failOnFlakyTests
Reporters can now detect whether the run failed because of flaky tests. This lets you emit a different Slack message: “Build passed with 2 flaky tests” instead of “Build failed.”
-G Shorthand for –grep-invert
A small CLI quality-of-life improvement. Instead of:
npx playwright test --grep-invert '@slow'
You can now write:
npx playwright test -G '@slow'
This is the kind of detail that adds up when you run tests 50 times a day.
Browser Versions in Playwright 1.61
Every Playwright release bundles fresh browser binaries. 1.61 ships:
- Chromium 149.0.7827.55
- Mozilla Firefox 151.0
- WebKit 26.5
It was also tested against the stable channels:
- Google Chrome 149
- Microsoft Edge 149
What this means practically: if your site recently started using CSS attr() fallbacks or the Popover API, these browser versions support them. If you are on Playwright 1.58, your tests run against Chromium 145. That is four major versions behind. Features your users see may not render in your tests.
I upgrade browser versions within 72 hours of every Playwright release. The cost of a false positive from an outdated engine is higher than the cost of running the upgrade.
Playwright 1.61.1 Patch: The Fixes That Matter
June 23, 2026 — eight days after the feature release, the team shipped 1.61.1 with five fixes:
- expect.extend overriding default matchers — If you registered a custom matcher with the same name as a built-in matcher, it corrupted the default implementation. Fixed.
- UI mode API request byte count mismatch — API requests in UI mode reported incorrect byte counts. The same test passed in headed mode. Fixed.
- Trace viewer WebSocket time scaling — WebSocket message timestamps in the trace viewer were divided by 1000, making a 1-second delay look like 1 millisecond. Fixed.
- Sync loader crash on Node 22.15 — The ESM loader threw
context.conditions?.includes is not a functionon Node 22.15. Fixed. - pnpm workspace symlink resolution — Extensionless
.tssubpath imports across pnpm workspace symlinks failed in the sync ESM loader. Fixed.
The Node 22.15 fix is the one that bit my team. We upgraded Node on our CI images the same day Playwright 1.61.0 dropped. Three hours later, every test suite using the sync loader crashed. We pinned Node back to 22.14 until 1.61.1 arrived. If you are on Node 22.15, treat 1.61.1 as mandatory, not optional.
Ubuntu 26.04 Support and WebSocket Traces
Two smaller features round out the release.
Ubuntu 26.04
Playwright 1.61 adds official support for Ubuntu 26.04. If your CI runners or Docker base images track the latest Ubuntu LTS, this removes the --ignore-system-dependencies workaround. Our Tekion pipeline moved to Ubuntu 26.04 base images in May. This release made the transition seamless.
WebSocket in HAR and Trace
HAR exports and trace recordings now include WebSocket traffic. This is critical for teams testing real-time applications — chat apps, trading dashboards, collaborative editors. Before 1.61, WebSocket frames were invisible in traces. Now you can inspect them the same way you inspect HTTP requests.
To verify this, record a trace on a WebSocket-heavy page and open it in the trace viewer. The Network panel now shows WebSocket frames with their payload, direction, and timestamp.
How to Upgrade Without Breaking Your Pipeline
Here is the upgrade checklist I run on every release:
- Pin the version in package.json — Do not use
^1.61.0. Use"playwright": "1.61.1". This prevents surprise upgrades when someone runsnpm install. - Run the full suite locally first —
npx playwright teston your machine before touching CI. Catches environment-specific failures. - Update Docker images — If you use
mcr.microsoft.com/playwrightimages, bump the tag tov1.61.1-jammyor-nobledepending on your base. - Re-install browsers —
npx playwright installfetches the new Chromium 149, Firefox 151, and WebKit 26.5 binaries. Skipping this step means your tests run against old browsers. - Audit for new deprecations — Check the official release notes for any methods marked deprecated. Playwright is good about deprecation warnings, but they become errors eventually.
- Enable new features incrementally — I add new APIs to a single spec file first. Once it passes for a week, I roll it out to the rest of the suite.
Our full upgrade from 1.60 to 1.61.1 took 45 minutes, including a coffee break. The only hiccup was the Node 22.15 sync loader issue, which 1.61.1 fixed before we even hit it in production.
The Hidden Cost of Staying on Old Versions
Every quarter I meet teams still on Playwright 1.55 or 1.56. They say the same thing: “It works, why upgrade?” Here is why. Playwright 1.55 runs Chromium 141. That is eight major versions behind Chromium 149. CSS features your production site uses — like attr() fallbacks, Popover API, and newer backdrop-filter behaviors — may not render in your tests. You get false positives: tests pass because the browser does not support a feature that breaks in production.
There is also a talent cost. When I interview senior SDETs, I ask about recent Playwright features. Candidates on 1.55 cannot answer. They look outdated. The testing industry moves fast. Staying current is a signal that you care about your craft.
Finally, security. Browser engines patch CVEs constantly. Chromium 141 has known vulnerabilities that Chromium 149 does not. If your tests run in CI pipelines with network access, an outdated browser is a risk vector.
India Context: What This Means for Your Playwright Job Interviews
I interview SDET candidates regularly. The questions I ask about Playwright get harder every quarter because the framework keeps growing. Here is what I would ask about 1.61 in a senior SDET round:
- “How would you test a passkey login flow without a real hardware key?” — The answer is
browserContext.credentials. If a candidate sayspage.evaluate, they are on an old version. - “What is the difference between
trace: 'retain-on-failure'andvideo: 'retain-on-failure-and-retries'?” — This tests whether they read the release notes or just copy configs from Stack Overflow. - “Why does
APIResponse.securityDetails()matter for API testing?” — If they cannot explain TLS validation, they have not thought about security automation.
On the salary side, Playwright specialization still commands a premium. In Bengaluru, a mid-level SDET with strong Playwright skills pulls ₹18–28 LPA at product companies. At service firms, the range is ₹10–16 LPA. The gap is widening because product companies want people who can handle passkeys, WebAuthn, and modern auth flows — not just click buttons and assert text.
If you are preparing for a switch, knowing 1.61 features puts you ahead of candidates still describing 1.58 APIs in interviews.
Key Takeaways
- Playwright 1.61 ships WebAuthn passkey testing via
browserContext.credentials. This is the headline feature. - The new WebStorage API replaces
page.evaluate(() => localStorage...)boilerplate with clean, type-safe methods. - APIResponse.securityDetails() and serverAddr() bring network introspection parity to API tests.
- Video modes now match trace modes, including
'retain-on-failure-and-retries'for flaky test debugging. - Playwright 1.61.1 fixes five regressions, including a Node 22.15 sync loader crash. Upgrade immediately if you are on Node 22.15.
- Browser versions jump to Chromium 149, Firefox 151, and WebKit 26.5.
- Ubuntu 26.04 is now officially supported.
- WebSocket traffic appears in HAR exports and trace recordings.
Frequently Asked Questions
Should I upgrade to Playwright 1.61.1 if I am on 1.60?
Yes. The passkey testing and WebStorage API alone justify the upgrade. If you are on Node 22.15, 1.61.1 is mandatory because of the sync loader fix.
Does Playwright 1.61 support Python and Java?
Yes. The WebAuthn, WebStorage, and network APIs are available in all language ports: TypeScript, JavaScript, Python, Java, and .NET. The API names are consistent across languages.
Can I test hardware-backed passkeys with Playwright 1.61?
No. The Credentials API creates a virtual authenticator. It simulates hardware-backed passkeys for testing purposes. It does not interact with real hardware keys like YubiKey.
Will video recording slow down my CI?
Marginally. Recording video adds about 5–10 percent overhead per test. If you enable 'retain-on-failure', video is only captured for failing tests, so the overhead is zero for the green runs that make up 95 percent of your suite.
How do I check my current Playwright version?
npx playwright --version
If the output is below 1.61.0, you are missing the features described in this article.
Where can I learn more about Playwright testing patterns?
If you are building a serious Playwright suite, read my guides on Playwright sharding for parallel CI and HTML and JUnit reporting. For teams migrating from Selenium, start with Part 1 of the migration guide.
