Skip to content

[Bug]: storageState with opfs: [] cannot be restored in WebKit, and capturing OPFS there fails outright #42579

Description

Version

1.63.0-next (reproduced on main at 46cd500; feature added in #42248)

Steps to reproduce

Two related problems with the new OPFS support. Both reproduce with the bundled test server.

1. A storage state carrying opfs: [] cannot be restored in WebKit.

const origin = 'http://localhost:PORT';
const withEmptyOpfs = { cookies: [], origins: [{ origin, localStorage: [{ name: 'k', value: 'v' }], opfs: [] }] };
const withoutOpfs   = { cookies: [], origins: [{ origin, localStorage: [{ name: 'k', value: 'v' }] }] };

await webkitBrowser.newContext({ storageState: withEmptyOpfs }); // throws
await webkitBrowser.newContext({ storageState: withoutOpfs });   // fine

2. storageState({ opfs: true }) throws on WebKit for every origin.

await webkitContext.storageState({ opfs: true });

Expected behavior

For 1, restoring an empty OPFS list is a no-op, so it should behave the same as omitting the field.
This matters because opfs: [] is what the other engines actually write. Capturing on Chromium and
reusing the file on WebKit is a normal workflow.

For 2, given the docs say "OPFS is currently not supported in ephemeral WebKit contexts"
(class-browsercontext.md:1594), capture should skip OPFS rather than fail the call.

Actual behavior

1. Identical states, differing only in that one field:

opfs: []    -> THREW: browser.newContext: Error setting storage state:
               Error: Unable to restore OPFS: The operation failed for an unknown transient reason (e.g. out of memory).
opfs absent -> OK, localStorage=v

And opfs: [] is exactly what the other engines produce for an origin with no OPFS files. Capturing
an origin that has localStorage but no OPFS gives:

browser=chromium originKeys=["origin","localStorage","indexedDB","opfs"] opfs=[]
browser=firefox  originKeys=["origin","localStorage","indexedDB","opfs"] opfs=[]

2. On WebKit:

Error: browserContext.storageState: Error: Unable to serialize OPFS:
The operation failed for an unknown transient reason (e.g. out of memory).

The whole call fails, including origins that never touched OPFS.

Additional context

For 1, the guard at packages/injected/src/storageScript.ts:226-237 looks like it already intends
the behaviour I would expect, but tests the wrong thing:

} catch (e) {
  // OPFS may be unavailable, e.g. on insecure origins or in WebKit contexts
  // that fail with 'unknown transient reason'. There is nothing to clear then,
  // so only fail when there are entries to restore.
  if (originState?.opfs === undefined)
    return;
  throw e;
}

The comment says "only fail when there are entries to restore", but the check is
=== undefined rather than emptiness, and opfs: [] has no entries. The protocol declares
opfs: array? (playwright.yml:353), so an empty array is a legal value distinct from an absent one.

For 2, collect() at storageScript.ts:183-189 wraps getDirectory() and rethrows as
Unable to serialize OPFS. On the server side the live-page loop swallows per-origin failures, but
the fallback blank-page loop in browserContext.ts has no equivalent catch, so it propagates. I am
less sure this one is unintended, since IndexedDB already threw the same way before this feature, so
treat it as the weaker of the two.

I have not opened a PR. My PRs here keep getting closed over an unsigned CLA on my side, so this is
more useful to you as a report. Happy to send a patch if that changes.

the second half is the part I would push back on myself, since it matches how IndexedDB already
behaved and may well be deliberate. the first one I am confident about, the comment and the code
disagree. freshman in college, thanks for reading :)

Environment

System:
  OS: macOS 26.5.2
  CPU: (10) arm64 Apple M4
Binaries:
  Node: 26.0.0
  npm: 11.12.1
npmPackages:
  playwright monorepo at main (1.63.0-next)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions