diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md
index cb945d673898c..1074568d209e7 100644
--- a/docs/src/api/class-page.md
+++ b/docs/src/api/class-page.md
@@ -2694,6 +2694,28 @@ Clears all stored console messages from this page. Subsequent calls to [`method:
 
 Clears all stored page errors from this page. Subsequent calls to [`method: Page.pageErrors`] will only return errors thrown after the clear.
 
+## property: Page.localStorage
+* since: v1.61
+- type: <[WebStorage]>
+
+Provides access to the page's `localStorage` for the current origin. See [WebStorage].
+
+```js
+await page.localStorage.setItem('token', 'abc');
+const token = await page.localStorage.getItem('token');
+```
+
+## property: Page.sessionStorage
+* since: v1.61
+- type: <[WebStorage]>
+
+Provides access to the page's `sessionStorage` for the current origin. See [WebStorage].
+
+```js
+await page.sessionStorage.setItem('flag', '1');
+const flag = await page.sessionStorage.getItem('flag');
+```
+
 ## async method: Page.consoleMessages
 * since: v1.56
 - returns: <[Array]<[ConsoleMessage]>>
diff --git a/docs/src/api/class-webstorage.md b/docs/src/api/class-webstorage.md
new file mode 100644
index 0000000000000..e84f77707d68f
--- /dev/null
+++ b/docs/src/api/class-webstorage.md
@@ -0,0 +1,105 @@
+# class: WebStorage
+* since: v1.61
+
+WebStorage exposes the page's `localStorage` or `sessionStorage` for the current origin via an async,
+[browser-consistent](https://developer.mozilla.org/en-US/docs/Web/API/Storage) API.
+
+Instances are accessed through [`property: Page.localStorage`] and [`property: Page.sessionStorage`].
+
+```js
+await page.goto('https://example.com');
+await page.localStorage.setItem('token', 'abc');
+const token = await page.localStorage.getItem('token');
+const all = await page.localStorage.items();
+await page.localStorage.removeItem('token');
+await page.localStorage.clear();
+```
+
+```python async
+await page.goto("https://example.com")
+await page.local_storage.set_item("token", "abc")
+token = await page.local_storage.get_item("token")
+all = await page.local_storage.items()
+await page.local_storage.remove_item("token")
+await page.local_storage.clear()
+```
+
+```python sync
+page.goto("https://example.com")
+page.local_storage.set_item("token", "abc")
+token = page.local_storage.get_item("token")
+all = page.local_storage.items()
+page.local_storage.remove_item("token")
+page.local_storage.clear()
+```
+
+```java
+page.navigate("https://example.com");
+page.localStorage().setItem("token", "abc");
+String token = page.localStorage().getItem("token");
+List<NameValue> all = page.localStorage().items();
+page.localStorage().removeItem("token");
+page.localStorage().clear();
+```
+
+```csharp
+await page.GotoAsync("https://example.com");
+await page.LocalStorage.SetItemAsync("token", "abc");
+var token = await page.LocalStorage.GetItemAsync("token");
+var all = await page.LocalStorage.ItemsAsync();
+await page.LocalStorage.RemoveItemAsync("token");
+await page.LocalStorage.ClearAsync();
+```
+
+## async method: WebStorage.items
+* since: v1.61
+- returns: <[Array]<[Object]>>
+  - `name` <[string]>
+  - `value` <[string]>
+
+Returns all items in the storage as `name`/`value` pairs.
+
+## async method: WebStorage.getItem
+* since: v1.61
+- returns: <[null]|[string]>
+
+Returns the value for the given `name`, or `null` if the key is not present.
+
+### param: WebStorage.getItem.name
+* since: v1.61
+- `name` <[string]>
+
+Name of the item to retrieve.
+
+## async method: WebStorage.setItem
+* since: v1.61
+
+Sets the value for the given `name`. Overwrites any existing value for that name.
+
+### param: WebStorage.setItem.name
+* since: v1.61
+- `name` <[string]>
+
+Name of the item to set.
+
+### param: WebStorage.setItem.value
+* since: v1.61
+- `value` <[string]>
+
+New value for the item.
+
+## async method: WebStorage.removeItem
+* since: v1.61
+
+Removes the item with the given `name`. No-op if the item is absent.
+
+### param: WebStorage.removeItem.name
+* since: v1.61
+- `name` <[string]>
+
+Name of the item to remove.
+
+## async method: WebStorage.clear
+* since: v1.61
+
+Removes all items from the storage.
diff --git a/packages/isomorphic/protocolMetainfo.ts b/packages/isomorphic/protocolMetainfo.ts
index 51ab50de3903e..5fa0617879e77 100644
--- a/packages/isomorphic/protocolMetainfo.ts
+++ b/packages/isomorphic/protocolMetainfo.ts
@@ -295,6 +295,11 @@ export const methodMetainfo = new Map<string, MethodMetainfo>([
   ['Page.screencastStop', { title: 'Stop screencast', group: 'configuration', }],
   ['Page.updateSubscription', { internal: true, }],
   ['Page.setDockTile', { internal: true, }],
+  ['Page.webStorageItems', { title: 'Get WebStorage items', group: 'getter', }],
+  ['Page.webStorageGetItem', { title: 'Get WebStorage item', group: 'getter', }],
+  ['Page.webStorageSetItem', { title: 'Set WebStorage item', group: 'configuration', }],
+  ['Page.webStorageRemoveItem', { title: 'Remove WebStorage item', group: 'configuration', }],
+  ['Page.webStorageClear', { title: 'Clear WebStorage', group: 'configuration', }],
   ['Root.initialize', { internal: true, }],
   ['Playwright.newRequest', { title: 'Create request context', }],
   ['DebugController.initialize', { internal: true, }],
diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts
index 8ac56c53a3ead..8ee8cce10ca1b 100644
--- a/packages/playwright-client/types/types.d.ts
+++ b/packages/playwright-client/types/types.d.ts
@@ -5244,6 +5244,18 @@ export interface Page {
 
   keyboard: Keyboard;
 
+  /**
+   * Provides access to the page's `localStorage` for the current origin. See
+   * [WebStorage](https://playwright.dev/docs/api/class-webstorage).
+   *
+   * ```js
+   * await page.localStorage.setItem('token', 'abc');
+   * const token = await page.localStorage.getItem('token');
+   * ```
+   *
+   */
+  localStorage: WebStorage;
+
   mouse: Mouse;
 
   /**
@@ -5272,6 +5284,18 @@ export interface Page {
    */
   screencast: Screencast;
 
+  /**
+   * Provides access to the page's `sessionStorage` for the current origin. See
+   * [WebStorage](https://playwright.dev/docs/api/class-webstorage).
+   *
+   * ```js
+   * await page.sessionStorage.setItem('flag', '1');
+   * const flag = await page.sessionStorage.getItem('flag');
+   * ```
+   *
+   */
+  sessionStorage: WebStorage;
+
   touchscreen: Touchscreen;
 
   [Symbol.asyncDispose](): Promise<void>;
@@ -21888,6 +21912,58 @@ export interface WebSocket {
 
 }
 
+/**
+ * WebStorage exposes the page's `localStorage` or `sessionStorage` for the current origin via an async,
+ * [browser-consistent](https://developer.mozilla.org/en-US/docs/Web/API/Storage) API.
+ *
+ * Instances are accessed through [page.localStorage](https://playwright.dev/docs/api/class-page#page-local-storage)
+ * and [page.sessionStorage](https://playwright.dev/docs/api/class-page#page-session-storage).
+ *
+ * ```js
+ * await page.goto('https://example.com');
+ * await page.localStorage.setItem('token', 'abc');
+ * const token = await page.localStorage.getItem('token');
+ * const all = await page.localStorage.items();
+ * await page.localStorage.removeItem('token');
+ * await page.localStorage.clear();
+ * ```
+ *
+ */
+export interface WebStorage {
+  /**
+   * Removes all items from the storage.
+   */
+  clear(): Promise<void>;
+
+  /**
+   * Returns the value for the given `name`, or `null` if the key is not present.
+   * @param name Name of the item to retrieve.
+   */
+  getItem(name: string): Promise<null|string>;
+
+  /**
+   * Returns all items in the storage as `name`/`value` pairs.
+   */
+  items(): Promise<Array<{
+    name: string;
+
+    value: string;
+  }>>;
+
+  /**
+   * Removes the item with the given `name`. No-op if the item is absent.
+   * @param name Name of the item to remove.
+   */
+  removeItem(name: string): Promise<void>;
+
+  /**
+   * Sets the value for the given `name`. Overwrites any existing value for that name.
+   * @param name Name of the item to set.
+   * @param value New value for the item.
+   */
+  setItem(name: string, value: string): Promise<void>;
+}
+
 /**
  * Playwright has **experimental** support for Electron automation. You can access electron namespace via:
  *
diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts
index 88335ab812fe3..f2b3811276990 100644
--- a/packages/playwright-core/src/client/page.ts
+++ b/packages/playwright-core/src/client/page.ts
@@ -34,6 +34,7 @@ import { FileChooser } from './fileChooser';
 import { Frame, verifyLoadState } from './frame';
 import { HarRouter } from './harRouter';
 import { Keyboard, Mouse, Touchscreen } from './input';
+import { WebStorage } from './webStorage';
 import { assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
 import { Request, Response, Route, RouteHandler, WebSocket,  WebSocketRoute, WebSocketRouteHandler, validateHeaders } from './network';
 import { Video } from './video';
@@ -97,6 +98,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
   readonly touchscreen: Touchscreen;
   readonly clock: Clock;
   readonly screencast: Screencast;
+  readonly localStorage: WebStorage;
+  readonly sessionStorage: WebStorage;
 
 
   readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
@@ -128,6 +131,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
     this.request = this._browserContext.request;
     this.touchscreen = new Touchscreen(this);
     this.clock = this._browserContext.clock;
+    this.localStorage = new WebStorage(this, 'local');
+    this.sessionStorage = new WebStorage(this, 'session');
 
     this._mainFrame = Frame.from(initializer.mainFrame);
     this._mainFrame._page = this;
diff --git a/packages/playwright-core/src/client/webStorage.ts b/packages/playwright-core/src/client/webStorage.ts
new file mode 100644
index 0000000000000..2edceaacf04bb
--- /dev/null
+++ b/packages/playwright-core/src/client/webStorage.ts
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { Page } from './page';
+import type * as api from '../../types/types';
+
+export class WebStorage implements api.WebStorage {
+  private _page: Page;
+  private _kind: 'local' | 'session';
+
+  constructor(page: Page, kind: 'local' | 'session') {
+    this._page = page;
+    this._kind = kind;
+  }
+
+  async items(): Promise<Array<{ name: string, value: string }>> {
+    const { items } = await this._page._channel.webStorageItems({ kind: this._kind });
+    return items;
+  }
+
+  async getItem(name: string): Promise<string | null> {
+    const { value } = await this._page._channel.webStorageGetItem({ kind: this._kind, name });
+    return value ?? null;
+  }
+
+  async setItem(name: string, value: string): Promise<void> {
+    await this._page._channel.webStorageSetItem({ kind: this._kind, name, value });
+  }
+
+  async removeItem(name: string): Promise<void> {
+    await this._page._channel.webStorageRemoveItem({ kind: this._kind, name });
+  }
+
+  async clear(): Promise<void> {
+    await this._page._channel.webStorageClear({ kind: this._kind });
+  }
+}
diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts
index 8a5a79ffb239f..c2e1b77177b64 100644
--- a/packages/playwright-core/src/protocol/validator.ts
+++ b/packages/playwright-core/src/protocol/validator.ts
@@ -2631,6 +2631,34 @@ scheme.PageSetDockTileParams = tObject({
   image: tBinary,
 });
 scheme.PageSetDockTileResult = tOptional(tObject({}));
+scheme.PageWebStorageItemsParams = tObject({
+  kind: tEnum(['local', 'session']),
+});
+scheme.PageWebStorageItemsResult = tObject({
+  items: tArray(tType('NameValue')),
+});
+scheme.PageWebStorageGetItemParams = tObject({
+  kind: tEnum(['local', 'session']),
+  name: tString,
+});
+scheme.PageWebStorageGetItemResult = tObject({
+  value: tOptional(tString),
+});
+scheme.PageWebStorageSetItemParams = tObject({
+  kind: tEnum(['local', 'session']),
+  name: tString,
+  value: tString,
+});
+scheme.PageWebStorageSetItemResult = tOptional(tObject({}));
+scheme.PageWebStorageRemoveItemParams = tObject({
+  kind: tEnum(['local', 'session']),
+  name: tString,
+});
+scheme.PageWebStorageRemoveItemResult = tOptional(tObject({}));
+scheme.PageWebStorageClearParams = tObject({
+  kind: tEnum(['local', 'session']),
+});
+scheme.PageWebStorageClearResult = tOptional(tObject({}));
 scheme.RootInitializer = tOptional(tObject({}));
 scheme.RootInitializeParams = tObject({
   sdkLanguage: tType('SDKLanguage'),
diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts
index 02c9da04dd207..1e2e7d0c0fe3c 100644
--- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts
+++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts
@@ -487,6 +487,26 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
   async setDockTile(params: channels.PageSetDockTileParams): Promise<void> {
     await this._page.setDockTile(params.image);
   }
+
+  async webStorageItems(params: channels.PageWebStorageItemsParams, progress: Progress): Promise<channels.PageWebStorageItemsResult> {
+    return { items: await this._page.webStorageItems(progress, params.kind) };
+  }
+
+  async webStorageGetItem(params: channels.PageWebStorageGetItemParams, progress: Progress): Promise<channels.PageWebStorageGetItemResult> {
+    return { value: await this._page.webStorageGetItem(progress, params.kind, params.name) };
+  }
+
+  async webStorageSetItem(params: channels.PageWebStorageSetItemParams, progress: Progress): Promise<void> {
+    await this._page.webStorageSetItem(progress, params.kind, params.name, params.value);
+  }
+
+  async webStorageRemoveItem(params: channels.PageWebStorageRemoveItemParams, progress: Progress): Promise<void> {
+    await this._page.webStorageRemoveItem(progress, params.kind, params.name);
+  }
+
+  async webStorageClear(params: channels.PageWebStorageClearParams, progress: Progress): Promise<void> {
+    await this._page.webStorageClear(progress, params.kind);
+  }
 }
 
 
diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts
index ed0f77776c5e0..898adbade9a52 100644
--- a/packages/playwright-core/src/server/page.ts
+++ b/packages/playwright-core/src/server/page.ts
@@ -929,6 +929,36 @@ export class Page extends SdkObject<PageEventMap> {
   async setDockTile(image: Buffer) {
     await this.delegate.setDockTile(image);
   }
+
+  async webStorageItems(progress: Progress, kind: 'local' | 'session'): Promise<{ name: string, value: string }[]> {
+    const storage = `${kind}Storage`;
+    return await this.mainFrame().evaluateExpression(progress, `(() => {
+      const result = [];
+      for (let i = 0; i < ${storage}.length; i++) {
+        const name = ${storage}.key(i);
+        if (name !== null)
+          result.push({ name, value: ${storage}.getItem(name) ?? '' });
+      }
+      return result;
+    })()`, { world: 'utility' });
+  }
+
+  async webStorageGetItem(progress: Progress, kind: 'local' | 'session', name: string): Promise<string | undefined> {
+    const value = await this.mainFrame().evaluateExpression(progress, `${kind}Storage.getItem(${JSON.stringify(name)})`, { world: 'utility' });
+    return value === null ? undefined : value;
+  }
+
+  async webStorageSetItem(progress: Progress, kind: 'local' | 'session', name: string, value: string): Promise<void> {
+    await this.mainFrame().evaluateExpression(progress, `${kind}Storage.setItem(${JSON.stringify(name)}, ${JSON.stringify(value)})`, { world: 'utility' });
+  }
+
+  async webStorageRemoveItem(progress: Progress, kind: 'local' | 'session', name: string): Promise<void> {
+    await this.mainFrame().evaluateExpression(progress, `${kind}Storage.removeItem(${JSON.stringify(name)})`, { world: 'utility' });
+  }
+
+  async webStorageClear(progress: Progress, kind: 'local' | 'session'): Promise<void> {
+    await this.mainFrame().evaluateExpression(progress, `${kind}Storage.clear()`, { world: 'utility' });
+  }
 }
 
 export const WorkerEvent = {
diff --git a/packages/playwright-core/src/tools/backend/webstorage.ts b/packages/playwright-core/src/tools/backend/webstorage.ts
index 956522811a51e..34003079b4793 100644
--- a/packages/playwright-core/src/tools/backend/webstorage.ts
+++ b/packages/playwright-core/src/tools/backend/webstorage.ts
@@ -29,21 +29,13 @@ const localStorageList = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    const items = await tab.page.evaluate(() => {
-      const result: { key: string; value: string }[] = [];
-      for (let i = 0; i < localStorage.length; i++) {
-        const key = localStorage.key(i);
-        if (key !== null)
-          result.push({ key, value: localStorage.getItem(key) || '' });
-      }
-      return result;
-    });
+    const items = await tab.page.localStorage.items();
 
     if (items.length === 0)
       response.addTextResult('No localStorage items found');
     else
-      response.addTextResult(items.map(item => `${item.key}=${item.value}`).join('\n'));
-    response.addCode(`await page.evaluate(() => ({ ...localStorage }));`);
+      response.addTextResult(items.map(item => `${item.name}=${item.value}`).join('\n'));
+    response.addCode(`await page.localStorage.items();`);
   },
 });
 
@@ -61,13 +53,13 @@ const localStorageGet = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    const value = await tab.page.evaluate(key => localStorage.getItem(key), params.key);
+    const value = await tab.page.localStorage.getItem(params.key);
 
     if (value === null)
       response.addTextResult(`localStorage key '${params.key}' not found`);
     else
       response.addTextResult(`${params.key}=${value}`);
-    response.addCode(`await page.evaluate(() => localStorage.getItem('${params.key}'));`);
+    response.addCode(`await page.localStorage.getItem('${params.key}');`);
   },
 });
 
@@ -86,8 +78,8 @@ const localStorageSet = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    await tab.page.evaluate(({ key, value }) => localStorage.setItem(key, value), params);
-    response.addCode(`await page.evaluate(() => localStorage.setItem('${params.key}', '${params.value}'));`);
+    await tab.page.localStorage.setItem(params.key, params.value);
+    response.addCode(`await page.localStorage.setItem('${params.key}', '${params.value}');`);
   },
 });
 
@@ -105,8 +97,8 @@ const localStorageDelete = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    await tab.page.evaluate(key => localStorage.removeItem(key), params.key);
-    response.addCode(`await page.evaluate(() => localStorage.removeItem('${params.key}'));`);
+    await tab.page.localStorage.removeItem(params.key);
+    response.addCode(`await page.localStorage.removeItem('${params.key}');`);
   },
 });
 
@@ -122,8 +114,8 @@ const localStorageClear = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    await tab.page.evaluate(() => localStorage.clear());
-    response.addCode(`await page.evaluate(() => localStorage.clear());`);
+    await tab.page.localStorage.clear();
+    response.addCode(`await page.localStorage.clear();`);
   },
 });
 
@@ -141,21 +133,13 @@ const sessionStorageList = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    const items = await tab.page.evaluate(() => {
-      const result: { key: string; value: string }[] = [];
-      for (let i = 0; i < sessionStorage.length; i++) {
-        const key = sessionStorage.key(i);
-        if (key !== null)
-          result.push({ key, value: sessionStorage.getItem(key) || '' });
-      }
-      return result;
-    });
+    const items = await tab.page.sessionStorage.items();
 
     if (items.length === 0)
       response.addTextResult('No sessionStorage items found');
     else
-      response.addTextResult(items.map(item => `${item.key}=${item.value}`).join('\n'));
-    response.addCode(`await page.evaluate(() => ({ ...sessionStorage }));`);
+      response.addTextResult(items.map(item => `${item.name}=${item.value}`).join('\n'));
+    response.addCode(`await page.sessionStorage.items();`);
   },
 });
 
@@ -173,13 +157,13 @@ const sessionStorageGet = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    const value = await tab.page.evaluate(key => sessionStorage.getItem(key), params.key);
+    const value = await tab.page.sessionStorage.getItem(params.key);
 
     if (value === null)
       response.addTextResult(`sessionStorage key '${params.key}' not found`);
     else
       response.addTextResult(`${params.key}=${value}`);
-    response.addCode(`await page.evaluate(() => sessionStorage.getItem('${params.key}'));`);
+    response.addCode(`await page.sessionStorage.getItem('${params.key}');`);
   },
 });
 
@@ -198,8 +182,8 @@ const sessionStorageSet = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    await tab.page.evaluate(({ key, value }) => sessionStorage.setItem(key, value), params);
-    response.addCode(`await page.evaluate(() => sessionStorage.setItem('${params.key}', '${params.value}'));`);
+    await tab.page.sessionStorage.setItem(params.key, params.value);
+    response.addCode(`await page.sessionStorage.setItem('${params.key}', '${params.value}');`);
   },
 });
 
@@ -217,8 +201,8 @@ const sessionStorageDelete = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    await tab.page.evaluate(key => sessionStorage.removeItem(key), params.key);
-    response.addCode(`await page.evaluate(() => sessionStorage.removeItem('${params.key}'));`);
+    await tab.page.sessionStorage.removeItem(params.key);
+    response.addCode(`await page.sessionStorage.removeItem('${params.key}');`);
   },
 });
 
@@ -234,8 +218,8 @@ const sessionStorageClear = defineTabTool({
   },
 
   handle: async (tab, params, response) => {
-    await tab.page.evaluate(() => sessionStorage.clear());
-    response.addCode(`await page.evaluate(() => sessionStorage.clear());`);
+    await tab.page.sessionStorage.clear();
+    response.addCode(`await page.sessionStorage.clear();`);
   },
 });
 
diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts
index 8ac56c53a3ead..8ee8cce10ca1b 100644
--- a/packages/playwright-core/types/types.d.ts
+++ b/packages/playwright-core/types/types.d.ts
@@ -5244,6 +5244,18 @@ export interface Page {
 
   keyboard: Keyboard;
 
+  /**
+   * Provides access to the page's `localStorage` for the current origin. See
+   * [WebStorage](https://playwright.dev/docs/api/class-webstorage).
+   *
+   * ```js
+   * await page.localStorage.setItem('token', 'abc');
+   * const token = await page.localStorage.getItem('token');
+   * ```
+   *
+   */
+  localStorage: WebStorage;
+
   mouse: Mouse;
 
   /**
@@ -5272,6 +5284,18 @@ export interface Page {
    */
   screencast: Screencast;
 
+  /**
+   * Provides access to the page's `sessionStorage` for the current origin. See
+   * [WebStorage](https://playwright.dev/docs/api/class-webstorage).
+   *
+   * ```js
+   * await page.sessionStorage.setItem('flag', '1');
+   * const flag = await page.sessionStorage.getItem('flag');
+   * ```
+   *
+   */
+  sessionStorage: WebStorage;
+
   touchscreen: Touchscreen;
 
   [Symbol.asyncDispose](): Promise<void>;
@@ -21888,6 +21912,58 @@ export interface WebSocket {
 
 }
 
+/**
+ * WebStorage exposes the page's `localStorage` or `sessionStorage` for the current origin via an async,
+ * [browser-consistent](https://developer.mozilla.org/en-US/docs/Web/API/Storage) API.
+ *
+ * Instances are accessed through [page.localStorage](https://playwright.dev/docs/api/class-page#page-local-storage)
+ * and [page.sessionStorage](https://playwright.dev/docs/api/class-page#page-session-storage).
+ *
+ * ```js
+ * await page.goto('https://example.com');
+ * await page.localStorage.setItem('token', 'abc');
+ * const token = await page.localStorage.getItem('token');
+ * const all = await page.localStorage.items();
+ * await page.localStorage.removeItem('token');
+ * await page.localStorage.clear();
+ * ```
+ *
+ */
+export interface WebStorage {
+  /**
+   * Removes all items from the storage.
+   */
+  clear(): Promise<void>;
+
+  /**
+   * Returns the value for the given `name`, or `null` if the key is not present.
+   * @param name Name of the item to retrieve.
+   */
+  getItem(name: string): Promise<null|string>;
+
+  /**
+   * Returns all items in the storage as `name`/`value` pairs.
+   */
+  items(): Promise<Array<{
+    name: string;
+
+    value: string;
+  }>>;
+
+  /**
+   * Removes the item with the given `name`. No-op if the item is absent.
+   * @param name Name of the item to remove.
+   */
+  removeItem(name: string): Promise<void>;
+
+  /**
+   * Sets the value for the given `name`. Overwrites any existing value for that name.
+   * @param name Name of the item to set.
+   * @param value New value for the item.
+   */
+  setItem(name: string, value: string): Promise<void>;
+}
+
 /**
  * Playwright has **experimental** support for Electron automation. You can access electron namespace via:
  *
diff --git a/packages/protocol/spec/page.yml b/packages/protocol/spec/page.yml
index 40937012762bd..77c0d94d64190 100644
--- a/packages/protocol/spec/page.yml
+++ b/packages/protocol/spec/page.yml
@@ -607,6 +607,66 @@ Page:
       parameters:
         image: binary
 
+    webStorageItems:
+      title: Get WebStorage items
+      group: getter
+      parameters:
+        kind:
+          type: enum
+          literals:
+          - local
+          - session
+      returns:
+        items:
+          type: array
+          items: NameValue
+
+    webStorageGetItem:
+      title: Get WebStorage item
+      group: getter
+      parameters:
+        kind:
+          type: enum
+          literals:
+          - local
+          - session
+        name: string
+      returns:
+        value: string?
+
+    webStorageSetItem:
+      title: Set WebStorage item
+      group: configuration
+      parameters:
+        kind:
+          type: enum
+          literals:
+          - local
+          - session
+        name: string
+        value: string
+
+    webStorageRemoveItem:
+      title: Remove WebStorage item
+      group: configuration
+      parameters:
+        kind:
+          type: enum
+          literals:
+          - local
+          - session
+        name: string
+
+    webStorageClear:
+      title: Clear WebStorage
+      group: configuration
+      parameters:
+        kind:
+          type: enum
+          literals:
+          - local
+          - session
+
   events:
 
     bindingCall:
diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts
index 9cfb1c11c73cf..d3acc2cc95acc 100644
--- a/packages/protocol/src/channels.d.ts
+++ b/packages/protocol/src/channels.d.ts
@@ -4034,6 +4034,11 @@ export interface PageChannel extends PageEventTarget, Channel {
   screencastStop(params?: PageScreencastStopParams, progress?: Progress): Promise<PageScreencastStopResult>;
   updateSubscription(params: PageUpdateSubscriptionParams, progress?: Progress): Promise<PageUpdateSubscriptionResult>;
   setDockTile(params: PageSetDockTileParams, progress?: Progress): Promise<PageSetDockTileResult>;
+  webStorageItems(params: PageWebStorageItemsParams, progress?: Progress): Promise<PageWebStorageItemsResult>;
+  webStorageGetItem(params: PageWebStorageGetItemParams, progress?: Progress): Promise<PageWebStorageGetItemResult>;
+  webStorageSetItem(params: PageWebStorageSetItemParams, progress?: Progress): Promise<PageWebStorageSetItemResult>;
+  webStorageRemoveItem(params: PageWebStorageRemoveItemParams, progress?: Progress): Promise<PageWebStorageRemoveItemResult>;
+  webStorageClear(params: PageWebStorageClearParams, progress?: Progress): Promise<PageWebStorageClearResult>;
 }
 export type PageBindingCallEvent = {
   binding: BindingCallChannel,
@@ -4639,6 +4644,49 @@ export type PageSetDockTileOptions = {
 
 };
 export type PageSetDockTileResult = void;
+export type PageWebStorageItemsParams = {
+  kind: 'local' | 'session',
+};
+export type PageWebStorageItemsOptions = {
+
+};
+export type PageWebStorageItemsResult = {
+  items: NameValue[],
+};
+export type PageWebStorageGetItemParams = {
+  kind: 'local' | 'session',
+  name: string,
+};
+export type PageWebStorageGetItemOptions = {
+
+};
+export type PageWebStorageGetItemResult = {
+  value?: string,
+};
+export type PageWebStorageSetItemParams = {
+  kind: 'local' | 'session',
+  name: string,
+  value: string,
+};
+export type PageWebStorageSetItemOptions = {
+
+};
+export type PageWebStorageSetItemResult = void;
+export type PageWebStorageRemoveItemParams = {
+  kind: 'local' | 'session',
+  name: string,
+};
+export type PageWebStorageRemoveItemOptions = {
+
+};
+export type PageWebStorageRemoveItemResult = void;
+export type PageWebStorageClearParams = {
+  kind: 'local' | 'session',
+};
+export type PageWebStorageClearOptions = {
+
+};
+export type PageWebStorageClearResult = void;
 
 export interface PageEvents {
   'bindingCall': PageBindingCallEvent;
diff --git a/tests/page/page-localstorage.spec.ts b/tests/page/page-localstorage.spec.ts
new file mode 100644
index 0000000000000..0791d7034f329
--- /dev/null
+++ b/tests/page/page-localstorage.spec.ts
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { test as it, expect } from './pageTest';
+
+it('localStorage.items returns empty array on fresh origin', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  expect(await page.localStorage.items()).toEqual([]);
+});
+
+it('localStorage.getItem returns null for missing key', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  expect(await page.localStorage.getItem('absent')).toBeNull();
+});
+
+it('localStorage.setItem persists and surfaces in items()/getItem()', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  await page.localStorage.setItem('alpha', '1');
+  await page.localStorage.setItem('beta', '2');
+
+  expect(new Set(await page.localStorage.items())).toEqual(new Set([
+    { name: 'alpha', value: '1' },
+    { name: 'beta', value: '2' },
+  ]));
+  expect(await page.localStorage.getItem('alpha')).toBe('1');
+  expect(await page.evaluate(() => localStorage.getItem('alpha'))).toBe('1');
+});
+
+it('localStorage.setItem overwrites existing value', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  await page.localStorage.setItem('k', 'first');
+  await page.localStorage.setItem('k', 'second');
+  expect(await page.localStorage.getItem('k')).toBe('second');
+});
+
+it('localStorage.removeItem removes a single item', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  await page.localStorage.setItem('a', '1');
+  await page.localStorage.setItem('b', '2');
+
+  await page.localStorage.removeItem('a');
+  expect(await page.localStorage.items()).toEqual([{ name: 'b', value: '2' }]);
+});
+
+it('localStorage.clear empties storage', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  await page.localStorage.setItem('a', '1');
+  await page.localStorage.setItem('b', '2');
+
+  await page.localStorage.clear();
+  expect(await page.localStorage.items()).toEqual([]);
+});
+
+it('sessionStorage round-trip', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  expect(await page.sessionStorage.items()).toEqual([]);
+
+  await page.sessionStorage.setItem('s1', 'v1');
+  await page.sessionStorage.setItem('s2', 'v2');
+  expect(new Set(await page.sessionStorage.items())).toEqual(new Set([
+    { name: 's1', value: 'v1' },
+    { name: 's2', value: 'v2' },
+  ]));
+  expect(await page.sessionStorage.getItem('s1')).toBe('v1');
+
+  await page.sessionStorage.removeItem('s1');
+  expect(await page.sessionStorage.items()).toEqual([{ name: 's2', value: 'v2' }]);
+
+  await page.sessionStorage.clear();
+  expect(await page.sessionStorage.items()).toEqual([]);
+});
+
+it('localStorage and sessionStorage are independent', async ({ page, server }) => {
+  await page.goto(server.EMPTY_PAGE);
+  await page.localStorage.setItem('shared', 'local');
+  await page.sessionStorage.setItem('shared', 'session');
+
+  expect(await page.localStorage.getItem('shared')).toBe('local');
+  expect(await page.sessionStorage.getItem('shared')).toBe('session');
+
+  await page.localStorage.clear();
+  expect(await page.localStorage.items()).toEqual([]);
+  expect(await page.sessionStorage.getItem('shared')).toBe('session');
+});
+
+it('storage methods are scoped to the current origin', async ({ page, server }) => {
+  await page.goto(server.PREFIX + '/empty.html');
+  await page.localStorage.setItem('k', 'origin-1');
+
+  await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+  expect(await page.localStorage.items()).toEqual([]);
+  await page.localStorage.setItem('k', 'origin-2');
+
+  await page.goto(server.PREFIX + '/empty.html');
+  expect(await page.localStorage.getItem('k')).toBe('origin-1');
+});
