Halmurat T.
Halmurat T.

Senior SDET

Home Blog Books ask About

The Dispatch

Weekly QA notes from the trenches.

Welcome aboard!

You're on the list. Expect real-world QA insights — no fluff, no spam.

© 2026 Halmurat T.

Automation 29
  • Selenium
  • Playwright
  • Appium
  • Cypress
AI Testing 68
CI/CD 13
  • GitHub Actions
  • Slack Reporting
QA Strategy 11
Case Studies 9
Blog/Automation
AutomationHalmurat T./November 10, 2026/10 min

Playwright 1.64 Breaking Changes: Gate Every Upgrade

Filed underplaywright/testing/framework-design/cicd
Playwright 1.64 Breaking Changes: Gate Every Upgrade

Table of Contents
  • Why do Playwright minor versions break tests?
  • What are the Playwright 1.64 breaking changes?
  • The screen change is the sneaky one
  • Isn’t Chrome for Testing proof that upgrades are safe?
  • Why isn’t a caret range or lockfile enough?
  • How does a canary lane gate a Playwright upgrade?
  • Step 1: Hold the upgrade PR
  • Step 2: Run the candidate nightly, sharded and non-blocking
  • Step 3: Fail the canary on silent greens
  • Step 4: Classify, then promote
  • Is a canary lane overkill for a small suite?
  • 1.65 is already here
  • FAQ

On this page

  • Why do Playwright minor versions break tests?
  • What are the Playwright 1.64 breaking changes?
  • The screen change is the sneaky one
  • Isn’t Chrome for Testing proof that upgrades are safe?
  • Why isn’t a caret range or lockfile enough?
  • How does a canary lane gate a Playwright upgrade?
  • Step 1: Hold the upgrade PR
  • Step 2: Run the candidate nightly, sharded and non-blocking
  • Step 3: Fail the canary on silent greens
  • Step 4: Classify, then promote
  • Is a canary lane overkill for a small suite?
  • 1.65 is already here
  • FAQ

Four breaking changes in a minor release. Ten more in the next one. The Playwright 1.64 breaking changes alter what window.screen reports, how JSX in test files compiles, whether a missing snapshot fails the run, and what counts as visible, and a ^1.63.0 range pulls all four into CI the next time someone regenerates the lockfile.

My position: a Playwright minor version is a major version with a smaller number. Pin it exactly, run every new release in a non-blocking canary lane for a week, and promote it only when every difference from the pinned run has an explanation.

Why do Playwright minor versions break tests?

Because Playwright doesn’t promise semver stability between minors, and its release notes say so in plain sight. Eight minor versions shipped between 1.57 (November 2025) and 1.65 (November 2026), and five of them carry a “Breaking Changes” heading. Each minor also bundles a new browser build: 1.64 moved from Chromium 153 to 156 (release notes), so even a release with no breaking-changes section changes the engine your tests run against.

What are the Playwright 1.64 breaking changes?

Playwright 1.64 changed four defaults: device descriptors now forward screen, JSX follows your tsconfig.json, --update-snapshots=missing now passes, and elements inside hidden iframes count as hidden. Two of them fail loudly. The other two can make a run greener than it should be.

ChangeWhat you’ll seeDirection
screen forwarded from devices['Desktop Chrome']window.screen reports 1920×1080 instead of the 1280×720 viewportRed, or silently different app behaviour
JSX follows tsconfig.jsonComponent tests compile with the automatic react/jsx-runtimeRed
--update-snapshots=missing passesTests that only wrote a new baseline now passRed → green
Hidden iframes hide their contentstoBeVisible() fails, toBeHidden() starts passingRed, and red → green

The screen change is the sneaky one

In the 1.65 bundle, the Desktop Chrome descriptor declares a 1280×720 viewport and a 1920×1080 screen. Before 1.64, Playwright ignored the descriptor’s screen and fell back to the viewport. Now any app code that reads window.screen.width, or any CSS using device-width media queries, sees a bigger monitor. Analytics payloads and “desktop vs tablet” branching shift without a single locator breaking.

The opt-out is one line, screen: undefined after the spread. I’d add it in the upgrade PR and remove it deliberately later, so the behaviour change becomes its own reviewed commit instead of riding along with a version bump.

[ WARNING ]

The two red-to-green changes are why “the upgrade PR passed” is not evidence. A test that used to fail on a missing baseline, or a toBeHidden() assertion inside an iframe you hid by accident, now passes. Your CI dashboard gets greener and your coverage gets smaller.

Isn’t Chrome for Testing proof that upgrades are safe?

No. It’s the best counterexample I have. Playwright 1.57 switched from Chromium to Chrome for Testing builds, and the notes said existing tests “should continue to pass.” For almost everyone, they did.

At a Canadian insurer I worked with, the suite was about 1,400 tests across 8 shards, and 3 of those shards ran on Arm64 runners because they were cheaper. The 1.57 notes end with one line: on Arm64 Linux, Playwright keeps using Chromium. After the bump, the x64 shards ran Chrome for Testing and the Arm64 shards ran Chromium, both reporting the same Playwright version. Seventeen screenshot tests failed only on the Arm64 shards, and only when the scheduler happened to place them there. It took a day and a half to connect the failures to the runner architecture, and nothing in the version number pointed there.

No release note was wrong. A change everyone called safe went straight into the blocking pipeline, where the only options were “fix it now” or “revert.”

Why isn’t a caret range or lockfile enough?

A lockfile pins Playwright until someone runs npm update, deletes the lockfile to fix a merge conflict, or builds a fresh image from package.json. Then ^1.63.0 resolves to whatever is latest. The browser binaries follow the npm package, and the Playwright Docker image tag has to match it too, so an accidental minor bump is really three upgrades at once.

Pin the exact version and make the Docker tag move in the same commit:

package.json
"devDependencies": {
"@playwright/test": "^1.63.0"
"@playwright/test": "1.63.1"
}

Even an exact tag doesn’t freeze the image contents. The v1.64.1-noble tag was rebuilt with a different Chrome and Node.js major, so pin by digest if your CI pulls the image.

How does a canary lane gate a Playwright upgrade?

A canary lane runs your full suite nightly against the candidate Playwright version, next to the pinned run, with continue-on-error so it can never block a merge. You promote the candidate only after several nights where the canary’s results match the pinned run, or every difference is explained.

The Renovate PR’s CI run is one sample. A week of nightlies shows whether the flaky count moved.

Step 1: Hold the upgrade PR

Group every Playwright package so they move together, and stop Renovate from opening the PR until the first patch has had time to land. 1.64.1 came out a week after 1.64.0 and fixed a Chromium 155+ net::ERR_ABORTED regression with routed cross-origin redirects.

renovate.json
{
"packageRules": [
{
"matchPackageNames": ["@playwright/test", "playwright", "mcr.microsoft.com/playwright"],
"groupName": "playwright",
"minimumReleaseAge": "7 days",
"automerge": false
}
]
}

minimumReleaseAge only works when the datasource exposes a release timestamp, so check that the Docker image actually appears in your Dependency Dashboard.

Step 2: Run the candidate nightly, sharded and non-blocking

This is the job, not the whole workflow. Add your schedule: trigger, Node setup, and an artifact upload for the canary/ directory.

.github/workflows/playwright-canary.yml
canary:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm install --no-save @playwright/test@${{ vars.PLAYWRIGHT_CANARY }}
- run: npx playwright install --with-deps
- run: npx playwright test --shard=${{ matrix.shard }}/4 --reporter=json
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: canary/shard-${{ matrix.shard }}.json

PLAYWRIGHT_CANARY is a repository variable you change by hand, which makes it the promotion lever. Set it to 1.65.0 and the lane tests 1.65 tonight, with nobody’s PR waiting.

Step 3: Fail the canary on silent greens

Pass/fail isn’t enough when two of the four changes make runs greener. Have the pinned nightly write its JSON the same way into pinned/, download both sets of artifacts, and compare test counts and flaky counts:

scripts/compare-canary.mjs
import { readdirSync, readFileSync } from 'node:fs';
const totals = (dir) => readdirSync(dir)
.map((file) => JSON.parse(readFileSync(`${dir}/${file}`, 'utf8')).stats)
.reduce((sum, s) => ({
ran: sum.ran + s.expected + s.unexpected + s.flaky,
flaky: sum.flaky + s.flaky,
}), { ran: 0, flaky: 0 });
const [pinned, canary] = process.argv.slice(2).map(totals);
// A canary that runs fewer tests isn't greener. It's blind.
if (canary.ran !== pinned.ran || canary.flaky > pinned.flaky) {
console.error('Canary drifted', { pinned, canary });
process.exit(1);
}

If your CI uses --update-snapshots=missing, also fail when git status --porcelain shows a new file under any -snapshots/ directory. Since 1.64, those new files arrive in a passing run.

Step 4: Classify, then promote

Every canary failure goes into one of three buckets: our bug that the new version exposed, their deliberate change (adapt or opt out), or a Playwright regression (file an issue and hold). Promote when no failure is left unclassified. In practice that has taken me between two nights and two weeks, and the slow cases were always the ones that turned out to be real app bugs.

Is a canary lane overkill for a small suite?

For a single repo with fewer than about 200 tests, yes. Read the release notes, let the grouped Renovate PR run, and add the count check from Step 3.

The canary earns its keep once the suite spans several shards, runner architectures, or teams, which is exactly where the Arm64 split hid. At that size, a bad upgrade in the blocking pipeline costs every team a morning, and the canary costs four nightly jobs nobody waits on.

1.65 is already here

Playwright 1.65 shipped on November 5 with ten items under Breaking Changes, and it stopped testing on Node.js 20. Actions now wait for elements to stop being inert, cookie matching follows RFC 6265, and accessible names drop unrendered text. I covered the parts that change locators and agent snapshots in what Playwright 1.65 changed for agents. If you’re still on 1.63 and wanted 1.64’s --shuffle for finding shared-state bugs, you’re now taking fourteen breaking changes at once. That’s the strongest case for the canary.

Do one thing today: remove the caret from @playwright/test, add the canary job with PLAYWRIGHT_CANARY set to 1.65.0, and read the canary’s results on Monday before anyone opens the upgrade PR. More on Playwright framework design lives in the test automation archive.

FAQ

§ Frequently Asked FAQ
+ What are the Playwright 1.64 breaking changes?

Four: device descriptors now forward the screen size, JSX in test files follows tsconfig.json, --update-snapshots=missing now passes the run, and elements inside hidden iframes are considered hidden. See the official 1.64 release notes.

+ Should I pin @playwright/test to an exact version?

Yes. Playwright minor releases regularly contain breaking changes and new browser builds. Upgrade through a reviewed, grouped PR.

+ How do I keep the old screen behaviour after upgrading to 1.64?

Set screen: undefined in the use block after spreading the device descriptor. The context then falls back to the viewport size, as before 1.64.

+ Why does my Playwright upgrade PR pass but production tests miss bugs?

Some 1.64 changes turn failing runs into passing ones, such as missing snapshots in 'missing' mode and toBeHidden() inside hidden iframes. Compare test and flaky counts between versions, not just pass or fail.

§ Further Reading 03 of 03
01Automation

The Browser Errors Your Test Suite Never Catches

Your UI tests pass green while the console throws errors. Learn to catch JavaScript and page errors in Selenium and Playwright Java — before users do.

Read →
02Automation

Shuffle AI-Generated Playwright Tests on 4 Workers, Not 1

Shuffle AI-generated Playwright tests on one worker and nothing fails. At four workers, 21 broke. Here is the probe, the isolated-retry check and the lock fix.

Read →
03Automation

Migrating Off Cypress? Here's When to Keep It

An honest take on Cypress vs Playwright migrations from an SDET who's done three, including the signals that tell you not to migrate your suite just yet.

Read →

Don't miss a thing

Subscribe to get updates straight to your inbox.

HT

No spam · Unsubscribe anytime

Welcome aboard!

You're on the list. Expect real-world QA insights — no fluff, no spam.

§ Colophon

Halmurat T. — Senior SDET writing about test automation, CI/CD, and QA strategy from 10+ years in the enterprise trenches.

Set in
IBM Plex Sans, Lora, and IBM Plex Mono.
Built with
Astro, MDX, Tailwind CSS & Expressive Code. Served by Vercel.
Privacy
No cookies. No tracking scripts on the main thread — analytics run sandboxed via Partytown.
Source
github.com/Halmurat-Uyghur
Terminal
Try /ask to query Halmurat's notes in a shell prompt.

© 2026 Halmurat T. · Written in plain text, shipped in plain time.

Search
Esc

Search is not available in dev mode.

Run npm run build then npm run preview:local to test search locally.