Skip to content

Commit dc8836a

Browse files
fix(server): clearCookies({name}) should not transiently delete other cookies (#40955)
Co-authored-by: Simon Knott <info@simonknott.de>
1 parent 2d04b34 commit dc8836a

2 files changed

Lines changed: 90 additions & 7 deletions

File tree

packages/playwright-core/src/server/browserContext.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,11 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk
309309
}
310310

311311
async clearCookies(options: {name?: string | RegExp, domain?: string | RegExp, path?: string | RegExp}): Promise<void> {
312-
const currentCookies = await this._cookies();
313-
await this.doClearCookies();
312+
const hasFilter = options.name !== undefined || options.domain !== undefined || options.path !== undefined;
313+
if (!hasFilter) {
314+
await this.doClearCookies();
315+
return;
316+
}
314317

315318
const matches = (cookie: channels.NetworkCookie, prop: 'name' | 'domain' | 'path', value: string | RegExp | undefined) => {
316319
if (!value)
@@ -322,13 +325,21 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk
322325
return cookie[prop] === value;
323326
};
324327

325-
const cookiesToReadd = currentCookies.filter(cookie => {
326-
return !matches(cookie, 'name', options.name)
327-
|| !matches(cookie, 'domain', options.domain)
328-
|| !matches(cookie, 'path', options.path);
328+
const currentCookies = await this._cookies();
329+
const cookiesToExpire = currentCookies.filter(cookie => {
330+
return matches(cookie, 'name', options.name)
331+
&& matches(cookie, 'domain', options.domain)
332+
&& matches(cookie, 'path', options.path);
329333
});
330334

331-
await this.addCookies(cookiesToReadd);
335+
if (!cookiesToExpire.length)
336+
return;
337+
338+
await this.addCookies(cookiesToExpire.map(cookie => ({
339+
...cookie,
340+
value: '',
341+
expires: 0,
342+
})));
332343
}
333344

334345
setHTTPCredentials(progress: Progress, httpCredentials?: types.Credentials): Promise<void> {

tests/library/browsercontext-clearcookies.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,75 @@ it('should remove cookies by name and domain', async ({ context, page, server })
164164
await page.goto(server.CROSS_PROCESS_PREFIX);
165165
expect(await page.evaluate('document.cookie')).toBe('cookie1=1');
166166
});
167+
168+
it('should not transiently delete non-matching cookies when filtering', {
169+
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/40953' },
170+
}, async ({ context, page, server }) => {
171+
await context.addCookies([{
172+
name: 'keep_me',
173+
value: '1',
174+
domain: new URL(server.PREFIX).hostname,
175+
path: '/',
176+
},
177+
{
178+
name: 'delete_me',
179+
value: '2',
180+
domain: new URL(server.PREFIX).hostname,
181+
path: '/',
182+
}
183+
]);
184+
await page.goto(server.PREFIX);
185+
186+
const eventsHandle = await page.evaluateHandle(() => {
187+
const events: string[] = [];
188+
cookieStore.addEventListener('change', event => {
189+
for (const changed of event.changed)
190+
events.push(`changed ${changed.name}`);
191+
for (const deleted of event.deleted)
192+
events.push(`deleted ${deleted.name}`);
193+
});
194+
return events;
195+
});
196+
197+
await context.clearCookies({ name: 'delete_me' });
198+
await expect.poll(() => eventsHandle.jsonValue()).toEqual(['deleted delete_me']);
199+
expect(await page.evaluate('document.cookie')).toBe('keep_me=1');
200+
});
201+
202+
it('should remove partitioned cookies by name', {
203+
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/40953' },
204+
}, async ({ browser, httpsServer, browserName }) => {
205+
it.skip(browserName !== 'chromium', 'Partitioned cookies (CHIPS) are Chromium-specific');
206+
const context = await browser.newContext({ ignoreHTTPSErrors: true });
207+
await context.addCookies([
208+
{
209+
name: 'delete_me',
210+
value: '1',
211+
domain: httpsServer.HOSTNAME,
212+
path: '/',
213+
secure: true,
214+
sameSite: 'None',
215+
partitionKey: `https://${httpsServer.HOSTNAME}`,
216+
},
217+
{
218+
name: 'keep_me',
219+
value: '2',
220+
domain: httpsServer.HOSTNAME,
221+
path: '/',
222+
secure: true,
223+
sameSite: 'None',
224+
partitionKey: `https://${httpsServer.HOSTNAME}`,
225+
},
226+
]);
227+
const before = await context.cookies();
228+
expect(before).toEqual([
229+
expect.objectContaining({ name: 'delete_me', partitionKey: `https://${httpsServer.HOSTNAME}` }),
230+
expect.objectContaining({ name: 'keep_me', partitionKey: `https://${httpsServer.HOSTNAME}` }),
231+
]);
232+
233+
await context.clearCookies({ name: 'delete_me' });
234+
expect(await context.cookies()).toEqual([
235+
expect.objectContaining({ name: 'keep_me', partitionKey: `https://${httpsServer.HOSTNAME}` }),
236+
]);
237+
await context.close();
238+
});

0 commit comments

Comments
 (0)