feat(webdriver): support Page.deleteCookie() for WebDriver BiDi (#12031)

This commit is contained in:
Maksim Sadym 2024-03-04 15:38:49 +01:00 committed by GitHub
parent 7284576690
commit 7fe22b533d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 238 additions and 33 deletions

View File

@ -12,9 +12,9 @@ export interface DeleteCookiesRequest
## Properties ## Properties
| Property | Modifiers | Type | Description | Default | | Property | Modifiers | Type | Description | Default |
| -------- | --------------------- | ------ | --------------------------------------------------------------------------------------------------- | ------- | | -------- | --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| domain | <code>optional</code> | string | If specified, deletes only cookies with the exact domain. | | | domain | <code>optional</code> | string | If specified, deletes only cookies with the exact domain. | |
| name | | string | Name of the cookies to remove. | | | name | | string | Name of the cookies to remove. | |
| path | <code>optional</code> | string | If specified, deletes only cookies with the exact path. | | | path | <code>optional</code> | string | If specified, deletes only cookies with the exact path. | |
| url | <code>optional</code> | string | If specified, deletes all the cookies with the given name where domain and path match provided URL. | | | url | <code>optional</code> | string | If specified, deletes all the cookies with the given name where domain and path match provided URL. Otherwise, deletes only cookies related to the current page's domain. | |

View File

@ -28,6 +28,7 @@ import {Coverage} from '../cdp/Coverage.js';
import {EmulationManager} from '../cdp/EmulationManager.js'; import {EmulationManager} from '../cdp/EmulationManager.js';
import {Tracing} from '../cdp/Tracing.js'; import {Tracing} from '../cdp/Tracing.js';
import type {Cookie, CookieParam, CookieSameSite} from '../common/Cookie.js'; import type {Cookie, CookieParam, CookieSameSite} from '../common/Cookie.js';
import type {DeleteCookiesRequest} from '../common/Cookie.js';
import {UnsupportedOperation} from '../common/Errors.js'; import {UnsupportedOperation} from '../common/Errors.js';
import {EventEmitter} from '../common/EventEmitter.js'; import {EventEmitter} from '../common/EventEmitter.js';
import type {PDFOptions} from '../common/PDFOptions.js'; import type {PDFOptions} from '../common/PDFOptions.js';
@ -559,9 +560,6 @@ export class BidiPage extends Page {
), ),
}; };
// TODO: delete cookie before setting them.
// await this.deleteCookie(bidiCookie);
if (cookie.partitionKey !== undefined) { if (cookie.partitionKey !== undefined) {
await this.browserContext().userContext.setCookie( await this.browserContext().userContext.setCookie(
bidiCookie, bidiCookie,
@ -573,8 +571,32 @@ export class BidiPage extends Page {
} }
} }
override deleteCookie(): never { override async deleteCookie(
throw new UnsupportedOperation(); ...cookies: DeleteCookiesRequest[]
): Promise<void> {
await Promise.all(
cookies.map(async deleteCookieRequest => {
const cookieUrl = deleteCookieRequest.url ?? this.url();
const normalizedUrl = URL.canParse(cookieUrl)
? new URL(cookieUrl)
: undefined;
const domain = deleteCookieRequest.domain ?? normalizedUrl?.hostname;
assert(
domain !== undefined,
`At least one of the url and domain needs to be specified`
);
const filter = {
domain: domain,
name: deleteCookieRequest.name,
...(deleteCookieRequest.path !== undefined
? {path: deleteCookieRequest.path}
: {}),
};
await this.#frame.browsingContext.deleteCookie(filter);
})
);
} }
override async removeExposedFunction(name: string): Promise<void> { override async removeExposedFunction(name: string): Promise<void> {

View File

@ -547,4 +547,24 @@ export class BrowsingContext extends EventEmitter<{
this.#disposables.dispose(); this.#disposables.dispose();
super[disposeSymbol](); super[disposeSymbol]();
} }
@throwIfDisposed<BrowsingContext>(context => {
// SAFETY: Disposal implies this exists.
return context.#reason!;
})
async deleteCookie(
...cookieFilters: Bidi.Storage.CookieFilter[]
): Promise<void> {
await Promise.all(
cookieFilters.map(async filter => {
await this.#session.send('storage.deleteCookies', {
filter: filter,
partition: {
type: 'context',
context: this.id,
},
});
})
);
}
} }

View File

@ -137,6 +137,10 @@ export interface Commands {
returnType: Bidi.EmptyResult; returnType: Bidi.EmptyResult;
}; };
'storage.deleteCookies': {
params: Bidi.Storage.DeleteCookiesParameters;
returnType: Bidi.Storage.DeleteCookiesResult;
};
'storage.getCookies': { 'storage.getCookies': {
params: Bidi.Storage.GetCookiesParameters; params: Bidi.Storage.GetCookiesParameters;
returnType: Bidi.Storage.GetCookiesResult; returnType: Bidi.Storage.GetCookiesResult;

View File

@ -172,7 +172,7 @@ export interface DeleteCookiesRequest {
name: string; name: string;
/** /**
* If specified, deletes all the cookies with the given name where domain and path match * If specified, deletes all the cookies with the given name where domain and path match
* provided URL. * provided URL. Otherwise, deletes only cookies related to the current page's domain.
*/ */
url?: string; url?: string;
/** /**

View File

@ -276,6 +276,13 @@
"parameters": ["firefox", "webDriverBiDi"], "parameters": ["firefox", "webDriverBiDi"],
"expectations": ["SKIP"] "expectations": ["SKIP"]
}, },
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should delete cookie for specified URL regardless of the current page",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox"],
"expectations": ["SKIP"],
"comment": "The test relies on the default page partition key do not contain the source origin. This is not the case for Firefox."
},
{ {
"testIdPattern": "[coverage.spec] *", "testIdPattern": "[coverage.spec] *",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
@ -1081,22 +1088,46 @@
"expectations": ["FAIL"] "expectations": ["FAIL"]
}, },
{ {
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should work", "testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should delete cookie",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should work",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"], "parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"] "expectations": ["FAIL"],
"comment": "Firefox default partition key is inconsistent: #12004"
},
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should delete cookie",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"],
"comment": "Not supported with cdp"
},
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should delete cookie for specified URL",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Firefox default partition key is inconsistent: #12004"
},
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should delete cookie for specified URL",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"],
"comment": "Not supported with cdp"
},
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should not delete cookie for different domain",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Firefox default partition key is inconsistent: #12004"
},
{
"testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should not delete cookie for different domain",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"],
"comment": "Not supported with cdp"
}, },
{ {
"testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should be able to set insecure cookie for HTTP website", "testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should be able to set insecure cookie for HTTP website",
@ -1194,12 +1225,6 @@
"parameters": ["cdp", "firefox"], "parameters": ["cdp", "firefox"],
"expectations": ["FAIL", "PASS"] "expectations": ["FAIL", "PASS"]
}, },
{
"testIdPattern": "[defaultbrowsercontext.spec] DefaultBrowserContext page.deleteCookie() should work",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{ {
"testIdPattern": "[defaultbrowsercontext.spec] DefaultBrowserContext page.deleteCookie() should work", "testIdPattern": "[defaultbrowsercontext.spec] DefaultBrowserContext page.deleteCookie() should work",
"platforms": ["darwin", "linux", "win32"], "platforms": ["darwin", "linux", "win32"],

View File

@ -558,7 +558,7 @@ describe('Cookie specs', () => {
}); });
describe('Page.deleteCookie', function () { describe('Page.deleteCookie', function () {
it('should work', async () => { it('should delete cookie', async () => {
const {page, server} = await getTestState(); const {page, server} = await getTestState();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -584,5 +584,139 @@ describe('Cookie specs', () => {
'cookie1=1; cookie3=3' 'cookie1=1; cookie3=3'
); );
}); });
it('should not delete cookie for different domain', async () => {
const {page, server} = await getTestState();
const COOKIE_DESTINATION_URL = 'https://example.com';
const COOKIE_NAME = 'some_cookie_name';
await page.goto(server.EMPTY_PAGE);
// Set a cookie for the current page.
await page.setCookie({
name: COOKIE_NAME,
value: 'local page cookie value',
});
expect(await page.cookies()).toHaveLength(1);
// Set a cookie for different domain.
await page.setCookie({
url: COOKIE_DESTINATION_URL,
name: COOKIE_NAME,
value: 'COOKIE_DESTINATION_URL cookie value',
});
expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1);
await page.deleteCookie({name: COOKIE_NAME});
// Verify the cookie is deleted for the current page.
expect(await page.cookies()).toHaveLength(0);
// Verify the cookie is not deleted for different domain.
await expectCookieEquals(await page.cookies(COOKIE_DESTINATION_URL), [
{
name: COOKIE_NAME,
value: 'COOKIE_DESTINATION_URL cookie value',
domain: 'example.com',
path: '/',
sameParty: false,
expires: -1,
size: 51,
httpOnly: false,
secure: true,
session: true,
sourceScheme: 'Secure',
},
]);
});
it('should delete cookie for specified URL', async () => {
const {page, server} = await getTestState();
const COOKIE_DESTINATION_URL = 'https://example.com';
const COOKIE_NAME = 'some_cookie_name';
await page.goto(server.EMPTY_PAGE);
// Set a cookie for the current page.
await page.setCookie({
name: COOKIE_NAME,
value: 'some_cookie_value',
});
expect(await page.cookies()).toHaveLength(1);
// Set a cookie for specified URL.
await page.setCookie({
url: COOKIE_DESTINATION_URL,
name: COOKIE_NAME,
value: 'another_cookie_value',
});
expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1);
// Delete the cookie for specified URL.
await page.deleteCookie({
url: COOKIE_DESTINATION_URL,
name: COOKIE_NAME,
});
// Verify the cookie is deleted for specified URL.
expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(0);
// Verify the cookie is not deleted for the current page.
await expectCookieEquals(await page.cookies(), [
{
name: COOKIE_NAME,
value: 'some_cookie_value',
domain: 'localhost',
path: '/',
sameParty: false,
expires: -1,
size: 33,
httpOnly: false,
secure: false,
session: true,
sourceScheme: 'NonSecure',
},
]);
});
it('should delete cookie for specified URL regardless of the current page', async () => {
// This test verifies the page.deleteCookie method deletes cookies for the custom
// destination URL, even if it was set from another page. Depending on the cookie
// partitioning implementation, this test case does not pass, if source origin is in
// the default cookie partition.
const {page, server} = await getTestState();
const COOKIE_DESTINATION_URL = 'https://example.com';
const COOKIE_NAME = 'some_cookie_name';
const URL_1 = server.EMPTY_PAGE;
const URL_2 = server.CROSS_PROCESS_PREFIX + '/empty.html';
await page.goto(URL_1);
// Set a cookie for the COOKIE_DESTINATION from URL_1.
await page.setCookie({
url: COOKIE_DESTINATION_URL,
name: COOKIE_NAME,
value: 'Cookie from URL_1',
});
expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1);
await page.goto(URL_2);
// Set a cookie for the COOKIE_DESTINATION from URL_2.
await page.setCookie({
url: COOKIE_DESTINATION_URL,
name: COOKIE_NAME,
value: 'Cookie from URL_2',
});
expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1);
// Delete the cookie for the COOKIE_DESTINATION from URL_2.
await page.deleteCookie({
name: COOKIE_NAME,
url: COOKIE_DESTINATION_URL,
});
// Expect the cookie for the COOKIE_DESTINATION from URL_2 is deleted.
expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(0);
// Navigate back to the URL_1.
await page.goto(server.EMPTY_PAGE);
// Expect the cookie for the COOKIE_DESTINATION from URL_1 is deleted.
expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(0);
});
}); });
}); });