July 17, 2026
How to Test Cookie Partitioning, SameSite, and Third-Party Cookie Changes in Real Browsers
A practical guide to testing SameSite cookies, partitioned cookies, and third-party cookie changes in Playwright, Selenium, and real browsers, with debugging steps and edge cases.
Modern browsers have turned cookies from a simple state mechanism into a compatibility surface you have to actively test. If your app relies on login redirects, embedded widgets, cross-site iframes, federated identity, or analytics that set state from a different site, then cookie rules can change whether the flow works, fails silently, or only fails in one browser.
That is why teams now need a reproducible way to test cookie partitioning in browser automation, not just in one happy-path local setup. The core challenge is that modern cookie behavior depends on more than the cookie name and value. It also depends on the site context, top-level navigation, browser privacy settings, the cookie attributes you set, and whether the request happens in a first-party or third-party context.
This guide focuses on the practical side. It explains how SameSite cookies testing works, how partitioned cookies differ from legacy third-party cookies, and how to build checks in real browsers that tell you whether your login and embedded UI flows are still compatible. The examples use Playwright and Selenium because they are common browser automation tools, but the debugging approach applies broadly to any test stack.
What changed, and why tests started failing
For a long time, many web apps treated cookies as ambient state. If a browser sent the cookie, the server accepted it. That model breaks down when browsers apply privacy protections that limit cross-site cookie access.
Three concepts matter most:
- SameSite cookies, which tell the browser when to send a cookie across site boundaries.
- Third-party cookie changes, which increasingly block or restrict cookies set or sent in a third-party context.
- Partitioned cookies, where the browser stores state separately for each top-level site, rather than sharing one global third-party cookie jar.
The key shift is that a cookie can be valid, yet still not be available in the context where your application expects it.
A cookie problem often looks like an auth problem, a redirect loop, or a broken embed, but the root cause is usually context, not value.
This is why tests that only check a direct login page can miss real failures. You need at least one test path that includes a top-level site, a cross-site request, and a browser that enforces modern cookie behavior.
The cookie rules that matter for UI testing
SameSite in practice
SameSite controls when cookies are sent on cross-site requests. The common values are:
Strict, only sent in same-site contexts.Lax, sent on same-site requests and some top-level navigations.None, sent in cross-site contexts, but only when markedSecure.
In practice, Lax is common for login sessions because it allows many top-level navigation flows while reducing cross-site exposure. None; Secure is required for truly cross-site usage, such as third-party embeds or identity flows that rely on cross-site state.
The failure mode to watch for is a flow that works in a direct browser navigation, but fails when the same step happens inside an iframe, popup, or redirect chain. The browser may simply not attach the cookie you expected.
Partitioned cookies
Partitioned cookies are designed for use in third-party contexts while reducing cross-site tracking. The browser stores them separately based on the top-level site. That means the cookie is not globally shared across all websites that embed the same third-party.
This is useful for embedded widgets, federated auth components, and other cross-site integrations that need state without building a fully shared third-party cookie model.
For testing, the important detail is that partitioning changes the lookup key. A cookie may exist, but only in the partition associated with a specific top-level site. If you test only from one host, you may miss the fact that the same widget behaves differently on another host.
Third-party cookie changes
Browser vendors have been tightening third-party cookie access. The exact rollout and controls differ by browser and version, but the practical impact is consistent, some flows that depended on cross-site cookies no longer work by default.
That means your automation needs to answer questions like these:
- Does the login flow still complete when the browser rejects third-party cookies?
- Does the embedded widget retain session state across page loads?
- Does the browser fall back to another mechanism, such as a first-party redirect or token handoff?
If you are not testing those scenarios explicitly, you are testing a behavior that may already be gone in production browsers.
What a good test strategy should verify
A practical test strategy does not try to inspect every cookie in every browser. It verifies the behavior that matters to the user flow.
Start with these checks:
- Cookie creation, does the server set the expected attributes?
- Cookie send behavior, does the browser attach the cookie in the target context?
- User-visible effect, does the login, embed, or cross-site flow succeed?
- Fallback behavior, if the cookie is blocked, does the app fail clearly or recover?
- Browser differences, does the flow behave the same across Chrome, Firefox, and WebKit where relevant?
The strongest tests are end-to-end, because cookie bugs usually appear at the seams between app, browser, and identity provider. But a smaller protocol-level check can help isolate the problem when the UI test fails.
A reproducible test setup for real browsers
To test cookie behavior reliably, you need two different site origins. The simplest pattern is:
- Top-level app origin, for example
https://app.localtest.me - Third-party origin, for example
https://auth.localtest.meorhttps://embed.localtest.me
You can run both through local HTTPS using a wildcard development domain or separate local hostnames. HTTPS matters because modern cookie scenarios often require Secure, and browsers treat some cookie behavior differently on insecure origins.
A useful test fixture is:
- A top-level page that embeds a cross-site iframe or redirects to another origin.
- A server endpoint that sets cookies with explicit attributes.
- A status endpoint that returns whether the server saw the cookie on the incoming request.
That gives you a simple loop:
- browser loads top-level app
- app loads cross-site content or redirects to auth
- server sets cookie with chosen attributes
- next request checks whether cookie comes back
- test asserts the visible result and optionally the network state
Playwright example for a cross-site cookie flow
Playwright is useful here because it can inspect browser context state and network responses while still exercising real browser behavior.
import { test, expect } from '@playwright/test';
test('cross-site login preserves a SameSite=None cookie', async ({ page, context }) => {
await page.goto('https://app.localtest.me');
await page.getByRole(‘link’, { name: ‘Sign in’ }).click(); await page.waitForURL(‘https://auth.localtest.me/**’);
const cookies = await context.cookies(); const session = cookies.find(c => c.name === ‘session’);
expect(session?.sameSite).toBe(‘None’); expect(session?.secure).toBe(true);
await expect(page.getByText(‘Signed in’)).toBeVisible(); });
A few practical notes:
context.cookies()shows what Playwright can observe in the browser context, which is useful for verification.- It does not replace the server-side check. Always confirm that the cookie is actually sent back on a request, because presence in storage is not the same as successful use in a request.
- If the test passes locally but fails in CI, look for HTTPS, hostnames, browser version, and privacy settings first.
Selenium example with a server-side assertion
Selenium can be enough if your team already uses it and you only need a simple browser-level assertion. The important part is to verify the cookie at the server boundary, not only in browser storage.
from selenium import webdriver
from selenium.webdriver.common.by import By
browser = webdriver.Chrome() browser.get(‘https://app.localtest.me’)
browser.find_element(By.LINK_TEXT, ‘Sign in’).click()
Example check: the app exposes a debug status page after redirect.
browser.get(‘https://app.localtest.me/session-status’) assert ‘authenticated: true’ in browser.page_source
browser.quit()
If you need deeper cookie inspection in Selenium, you can use driver.get_cookies(), but remember the same limitation applies, a cookie in browser storage does not guarantee that the browser sent it in the right request context.
How to test SameSite cookies without false confidence
SameSite bugs are easy to miss because many test suites accidentally stay within one site boundary.
A decent SameSite test should include at least one of these flows:
- top-level navigation from one site to another
- redirect-based login, especially if the identity provider is on a different site
- iframe-based embed
- XHR or fetch request to a different origin, when cookies are expected
A concrete test pattern is to verify the request after a cross-site hop. For example, an auth server sets a cookie, then redirects back to the app, and the app checks whether the request included the cookie.
Server-side pseudocode for the auth response might look like this:
http Set-Cookie: session=abc123; Path=/; SameSite=None; Secure; HttpOnly
Then your app can call a /whoami endpoint and assert that the user is authenticated.
If the app uses SameSite=Lax, test both of these cases:
- a top-level navigation back to the app, which may still work
- an iframe or subresource request, which may not
That distinction often explains why a login redirect succeeds while an embedded refresh token check fails.
How to test partitioned cookies
Partitioned cookies are trickier because the same cookie name can exist in different partitions depending on the top-level site.
A good test plan looks like this:
- Open
site-a.exampleand load a third-party widget. - Let the widget set a partitioned cookie.
- Refresh or revisit
site-a.example, confirm the widget retains state. - Open
site-b.example, load the same widget, and confirm it starts with a separate state.
This is the behavior you want if the cookie is intentionally partitioned. If your application expects shared state across embedding sites, partitioning will break that assumption.
Partitioning is not just a storage detail, it changes the product contract for anything that used to depend on shared third-party state.
In browser automation, the biggest mistake is to assert only that a cookie exists. You need to assert that it exists in the expected partition and that the visible behavior matches that partitioning model.
Debugging checklist when a test fails
When a cookie-related UI test fails, work from the browser outward.
1. Check the cookie attributes
Inspect the Set-Cookie header and confirm the attributes match the intended use:
Securefor HTTPS-only cookiesHttpOnlyfor cookies that should not be readable in JavaScriptSameSite=Nonefor cross-site usagePartitionedif the browser and flow expect partitioned storage
One common mistake is using SameSite=None without Secure. Modern browsers reject that combination.
2. Check the request context
Ask whether the cookie is being used in:
- a top-level navigation
- a subresource request
- an iframe
- a redirect
- a cross-origin fetch
Same cookie, different browser rule.
3. Check browser privacy controls
Incognito mode, anti-tracking settings, enterprise policies, and browser version can all change behavior. For CI, make sure you know which browser channel and version you are running.
4. Check storage versus network behavior
A cookie can appear in storage but still not be sent in the request you care about. When debugging, capture both the browser-side state and the network response from the server.
5. Check the fallback path
If the cookie is blocked, does the app show a clear error, or does it spin forever? Good tests assert the user-visible fallback, not just the failure.
Practical Playwright debugging tools
Playwright gives you a few useful hooks for troubleshooting these flows.
page.on('request', request => {
if (request.url().includes('session-status')) {
console.log('Request headers:', request.headers());
}
});
page.on(‘response’, async response => { if (response.url().includes(‘login’)) { console.log(‘Status:’, response.status()); } });
That kind of logging helps confirm whether the browser is sending the cookie on the request that matters. If the server sees an unauthenticated request, the issue is usually not the UI locator. It is often cookie policy, redirect handling, or origin setup.
CI considerations that matter more than the test code
Cookie tests often fail in CI for reasons that have nothing to do with the assertions.
A reliable CI setup should control:
- browser version, because cookie behavior changes over time
- HTTPS support, because secure cookies require it
- DNS or host mapping, so different test sites are actually different sites
- reuse of browser context, because a stale context can hide partitioning bugs
- parallelism, because shared test domains can leak state between tests
A common failure mode is running all cookie tests against localhost with different ports. In some cases that is not enough to model the same site versus cross-site boundary you care about. Use distinct hostnames when the goal is to test site boundaries, not just app routing.
A simple GitHub Actions job can still be enough if you provision the right hosts and serve HTTPS correctly:
name: browser-cookie-tests
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx playwright install –with-deps chromium - run: npm test
If the app depends on browser-specific cookie behavior, run the relevant browsers in CI rather than assuming Chromium covers everything. Browser automation is a tool category, not a guarantee of identical behavior across engines. For a broader background on the discipline, see software testing and test automation.
When to test at the UI level, and when to go lower
Not every cookie issue needs a full UI flow. There are three layers worth using together:
UI flow test
Best for login, embeds, redirect loops, and user-visible state.
API or server assertion
Best for confirming Set-Cookie headers and request receipt.
Browser context inspection
Best for verifying what the browser stored and which attributes were applied.
The tradeoff is coverage versus brittleness. UI tests catch the real user experience, but they are slower and more fragile. Lower-level checks are faster and easier to diagnose, but they can miss context-specific failures. A robust team usually combines both.
Common failure modes to expect
Here are the failures that show up repeatedly in cookie-related browser automation:
SameSite=NonewithoutSecure- trying to read an
HttpOnlycookie from JavaScript, then assuming the cookie was not set - relying on a single
localhostorigin for a cross-site test - testing only top-level navigation when the real product uses iframes
- asserting cookie storage instead of server receipt
- forgetting that partitioned cookies are keyed by top-level site
- running CI in a browser mode that differs from local developer testing
If you treat those as expected failure modes rather than edge mysteries, debugging becomes much faster.
A small selection guide for teams
If your team is deciding how to cover cookie behavior, use this rule of thumb:
- Use Playwright if you want modern browser context APIs, strong cross-browser support, and clean control over redirects and storage state.
- Use Selenium if your organization already has a mature Selenium stack and the team is comfortable adding targeted cookie assertions.
- Use Cypress if the user flow fits its browser model, but be careful about cross-origin and cookie assumptions, because those can require extra setup.
- Use server-side integration checks for fast verification of cookie headers and auth handshakes.
The best choice is not the tool with the most features. It is the one that can express the exact site boundary and request context your app depends on, without hiding browser behavior behind mocks.
A practical test matrix to keep around
If you only keep one artifact from this article, make it a small matrix of scenarios:
- same-site login, expected to pass
- cross-site login with
SameSite=None; Secure, expected to pass - cross-site embed with partitioned cookie, expected to preserve state within one top-level site
- cross-site embed on a second top-level site, expected to isolate state
- cross-site flow with third-party cookies blocked, expected to fail cleanly or fall back
That matrix is small enough to maintain and specific enough to catch the class of bugs that modern browser privacy rules create.
Closing thoughts
Cookie behavior is now part of the browser contract, not just a server implementation detail. That is why teams that ship auth, embeds, or cross-site integrations need explicit tests for SameSite cookies testing, partitioned cookies, and third-party cookie changes.
The most useful mindset is to stop asking only, “Was the cookie set?” and start asking, “Did the browser send the cookie in the exact context my product depends on?” Once you test that question in real browsers, many mysterious login and embed bugs turn into reproducible, fixable cases.
If you keep your scenarios small, use real hostnames, inspect both storage and network behavior, and assert the user-visible result, cookie-related browser automation becomes much less guessy and much more dependable.