diff --git a/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts b/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts index 313f3ab0ae8a9..9309873ef9d22 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts @@ -25,6 +25,7 @@ import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '@utils/happyEye import { headersArrayToObject } from '@isomorphic/headers'; import { Browser } from '../../browser'; import { helper } from '../../helper'; +import * as network from '../../network'; import { perMessageDeflate } from '../../transport'; import { getUserAgent } from '../../userAgent'; import { BrowserContext } from '../../browserContext'; @@ -35,6 +36,7 @@ import { WVPage } from './wvPage'; import type { BrowserOptions, BrowserProcess } from '../../browser'; import type { SdkObject } from '../../instrumentation'; import type { InitScript, Page } from '../../page'; +import type { Protocol } from './protocol'; import type { ProtocolRequest, ProtocolResponse } from '../../transport'; import type * as types from '../../types'; import type * as channels from '../../channels'; @@ -305,16 +307,64 @@ export class WVBrowserContext extends BrowserContext { throw new Error('Not supported'); } + // The Page cookie commands only see cookies for the current page's domain, so + // cookie access is scoped to that page rather than the whole context. + private _cookiePage(): WVPage | undefined { + const page = this.pages()[0]; + return page ? page.delegate as WVPage : undefined; + } + async doGetCookies(urls: string[]): Promise { - return []; + const page = this._cookiePage(); + if (!page) + return []; + const cookies = await page.getCookies(); + return network.filterCookies(cookies.map(c => { + const copy: channels.NetworkCookie = { + name: c.name, + value: c.value, + domain: c.domain, + path: c.path, + expires: c.session ? -1 : c.expires / 1000, + httpOnly: c.httpOnly, + secure: c.secure, + sameSite: c.sameSite, + }; + return copy; + }), urls); } async addCookies(cookies: channels.SetNetworkCookie[]) { - throw new Error('Method not implemented.'); + const page = this._cookiePage(); + if (!page) + throw new Error('Cannot set cookies without an open page'); + const protocolCookies = network.rewriteCookies(cookies).map(c => { + const session = c.expires === undefined || c.expires === -1; + const cookie: Protocol.Page.Cookie = { + name: c.name, + value: c.value, + domain: c.domain!, + path: c.path!, + expires: session ? 0 : c.expires! * 1000, + session, + httpOnly: !!c.httpOnly, + secure: !!c.secure, + sameSite: c.sameSite ?? 'Lax', + }; + return cookie; + }); + await page.setCookies(protocolCookies); } async doClearCookies() { - throw new Error('Method not implemented.'); + const page = this._cookiePage(); + if (!page) + return; + const cookies = await page.getCookies(); + await page.deleteCookies(cookies.map(c => ({ + cookieName: c.name, + url: `${c.secure ? 'https' : 'http'}://${c.domain.replace(/^\./, '')}${c.path}`, + }))); } async doGrantPermissions(origin: string, permissions: string[]) { diff --git a/packages/playwright-core/src/server/webkit/webview/wvPage.ts b/packages/playwright-core/src/server/webkit/webview/wvPage.ts index 4d249e705ae4f..812f867aa4ace 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvPage.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvPage.ts @@ -586,6 +586,21 @@ export class WVPage implements PageDelegate { await this._session.send('Page.reload'); } + async getCookies(): Promise { + const { cookies } = await this._session.send('Page.getCookies'); + return cookies; + } + + async setCookies(cookies: Protocol.Page.Cookie[]): Promise { + for (const cookie of cookies) + await this._session.send('Page.setCookie', { cookie }); + } + + async deleteCookies(cookies: { cookieName: string, url: string }[]): Promise { + for (const { cookieName, url } of cookies) + await this._session.send('Page.deleteCookie', { cookieName, url }); + } + async addInitScript(initScript: InitScript): Promise { await this._updateBootstrapScript(); } diff --git a/tests/webview/expectations/webkit-webview-page.txt b/tests/webview/expectations/webkit-webview-page.txt index bd3c95dcdf560..0db259d0d6ef5 100644 --- a/tests/webview/expectations/webkit-webview-page.txt +++ b/tests/webview/expectations/webkit-webview-page.txt @@ -336,7 +336,6 @@ page/page-request-fulfill.spec.ts › should fulfill with multiple set-cookie [f page/page-request-intercept.spec.ts › should support timeout option in route.fetch [fail] page/page-route.spec.ts › should fail navigation when aborting main resource [fail] page/page-route.spec.ts › should not auto-intercept non-preflight OPTIONS with network interception [fail] -page/page-route.spec.ts › should not override cookie header [fail] page/page-route.spec.ts › should send referer [fail] page/page-screenshot.spec.ts › page screenshot animations › should capture screenshots after layoutchanges in transitionend event › make sure transition is actually running [fail] page/page-screenshot.spec.ts › page screenshot animations › should fire transitionend for finite transitions › make sure transition is actually running [fail] @@ -492,7 +491,6 @@ page/page-request-gc.spec.ts › should work [fail] page/page-request-intercept.spec.ts › should fulfill intercepted response using alias [fail] page/page-request-intercept.spec.ts › should intercept with url override [fail] page/page-request-intercept.spec.ts › should not follow redirects when maxRedirects is set to 0 in route.fetch [fail] -page/page-route.spec.ts › should properly return navigation response when URL has cookies [fail] page/page-set-input-files.spec.ts › should detect mime type [fail] page/page-set-input-files.spec.ts › should emit input and change events [fail] page/page-set-input-files.spec.ts › should upload a folder [fail] diff --git a/tests/webview/webviewTest.ts b/tests/webview/webviewTest.ts index 2fc530c00e670..b5e0e1c21d939 100644 --- a/tests/webview/webviewTest.ts +++ b/tests/webview/webviewTest.ts @@ -148,6 +148,9 @@ export const webviewTest = baseTest.extend {}); await run(page); + // The shared Mobile Safari cookie store persists across tests; clear it + // while still on the test's domain (webview cookies are domain-scoped). + await page.context().clearCookies().catch(() => {}); await browser.close(); },