feat(firefox): implement cookies api (#4076)

This patch implements `page.setCookie()`, `page.deleteCookie()` and
`page.cookies()` and doubles the test coverage for cookies so that
we can feel safer on cross-browser compatibility.
This commit is contained in:
Andrey Lushnikov 2019-02-26 16:24:30 -08:00 committed by GitHub
parent 03d06f54d6
commit 9ef23b1754
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 269 additions and 72 deletions

View File

@ -8,6 +8,7 @@ const util = require('util');
const EventEmitter = require('events'); const EventEmitter = require('events');
const {createHandle} = require('./JSHandle'); const {createHandle} = require('./JSHandle');
const {Events} = require('./Events'); const {Events} = require('./Events');
const {Connection} = require('./Connection');
const {FrameManager, normalizeWaitUntil} = require('./FrameManager'); const {FrameManager, normalizeWaitUntil} = require('./FrameManager');
const {NetworkManager} = require('./NetworkManager'); const {NetworkManager} = require('./NetworkManager');
const {TimeoutSettings} = require('./TimeoutSettings'); const {TimeoutSettings} = require('./TimeoutSettings');
@ -80,6 +81,67 @@ class Page extends EventEmitter {
}); });
} }
/**
* @param {!Array<string>} urls
* @return {!Promise<!Array<Network.Cookie>>}
*/
async cookies(...urls) {
const connection = Connection.fromSession(this._session);
return (await connection.send('Browser.getCookies', {
browserContextId: this._target._context._browserContextId,
urls: urls.length ? urls : [this.url()]
})).cookies;
}
/**
* @param {Array<Protocol.Network.deleteCookiesParameters>} cookies
*/
async deleteCookie(...cookies) {
const pageURL = this.url();
const items = [];
for (const cookie of cookies) {
const item = {
url: cookie.url,
domain: cookie.domain,
path: cookie.path,
name: cookie.name,
};
if (!item.url && pageURL.startsWith('http'))
item.url = pageURL;
items.push(item);
}
const connection = Connection.fromSession(this._session);
await connection.send('Browser.deleteCookies', {
browserContextId: this._target._context._browserContextId,
cookies: items,
});
}
/**
* @param {Array<Network.CookieParam>} cookies
*/
async setCookie(...cookies) {
const pageURL = this.url();
const startsWithHTTP = pageURL.startsWith('http');
const items = cookies.map(cookie => {
const item = Object.assign({}, cookie);
if (!item.url && startsWithHTTP)
item.url = pageURL;
assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
return item;
});
await this.deleteCookie(...items);
if (items.length) {
const connection = Connection.fromSession(this._session);
await connection.send('Browser.setCookies', {
browserContextId: this._target._context._browserContextId,
cookies: items
});
}
}
async setRequestInterception(enabled) { async setRequestInterception(enabled) {
await this._networkManager.setRequestInterception(enabled); await this._networkManager.setRequestInterception(enabled);
} }

View File

@ -9,7 +9,7 @@
"node": ">=8.9.4" "node": ">=8.9.4"
}, },
"puppeteer": { "puppeteer": {
"firefox_revision": "d69636bbb91f42286e81ef673b33a1459bcdfcea" "firefox_revision": "fd63770c54de8a6e4ac28bd9f010405c12105d63"
}, },
"scripts": { "scripts": {
"install": "node install.js", "install": "node install.js",

View File

@ -388,14 +388,8 @@ class Page extends EventEmitter {
const item = Object.assign({}, cookie); const item = Object.assign({}, cookie);
if (!item.url && startsWithHTTP) if (!item.url && startsWithHTTP)
item.url = pageURL; item.url = pageURL;
assert( assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
item.url !== 'about:blank', assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
`Blank page can not have cookie "${item.name}"`
);
assert(
!String.prototype.startsWith.call(item.url || '', 'data:'),
`Data URL page can not have cookie "${item.name}"`
);
return item; return item;
}); });
await this.deleteCookie(...items); await this.deleteCookie(...items);

View File

@ -18,7 +18,7 @@ const utils = require('./utils');
module.exports.addTests = function({testRunner, expect, puppeteer, Errors}) { module.exports.addTests = function({testRunner, expect, puppeteer, Errors}) {
const {describe, xdescribe, fdescribe} = testRunner; const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit, it_fails_ffox} = testRunner; const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
const {TimeoutError} = Errors; const {TimeoutError} = Errors;

View File

@ -15,14 +15,17 @@
*/ */
module.exports.addTests = function({testRunner, expect}) { module.exports.addTests = function({testRunner, expect}) {
const {describe, xdescribe, fdescribe, describe_fails_ffox} = testRunner; const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner; const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe_fails_ffox('Cookies', function() { describe('Page.cookies', function() {
it('should set and get cookies', async({page, server}) => { it('should return no cookies in pristine browser context', async({page, server}) => {
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.EMPTY_PAGE);
expect(await page.cookies()).toEqual([]); expect(await page.cookies()).toEqual([]);
});
it('should get a cookie', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => { await page.evaluate(() => {
document.cookie = 'username=John Doe'; document.cookie = 'username=John Doe';
}); });
@ -35,13 +38,144 @@ module.exports.addTests = function({testRunner, expect}) {
size: 16, size: 16,
httpOnly: false, httpOnly: false,
secure: false, secure: false,
session: true } session: true
}]);
});
it('should get multiple cookies', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie = 'username=John Doe';
document.cookie = 'password=1234';
});
const cookies = await page.cookies();
cookies.sort((a, b) => a.name.localeCompare(b.name));
expect(cookies).toEqual([
{
name: 'password',
value: '1234',
domain: 'localhost',
path: '/',
expires: -1,
size: 12,
httpOnly: false,
secure: false,
session: true
},
{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
size: 16,
httpOnly: false,
secure: false,
session: true
},
]); ]);
});
it('should get cookies from multiple urls', async({page, server}) => {
await page.setCookie({
url: 'https://foo.com',
name: 'doggo',
value: 'woofs',
}, {
url: 'https://bar.com',
name: 'catto',
value: 'purrs',
}, {
url: 'https://baz.com',
name: 'birdo',
value: 'tweets',
});
const cookies = await page.cookies('https://foo.com', 'https://baz.com');
cookies.sort((a, b) => a.name.localeCompare(b.name));
expect(cookies).toEqual([{
name: 'birdo',
value: 'tweets',
domain: 'baz.com',
path: '/',
expires: -1,
size: 11,
httpOnly: false,
secure: true,
session: true
}, {
name: 'doggo',
value: 'woofs',
domain: 'foo.com',
path: '/',
expires: -1,
size: 10,
httpOnly: false,
secure: true,
session: true
}]);
});
});
describe('Page.setCookie', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setCookie({
name: 'password',
value: '123456'
});
expect(await page.evaluate(() => document.cookie)).toEqual('password=123456');
});
it('should isolate cookies in browser contexts', async({page, server, browser}) => {
const anotherContext = await browser.createIncognitoBrowserContext();
const anotherPage = await anotherContext.newPage();
await page.goto(server.EMPTY_PAGE);
await anotherPage.goto(server.EMPTY_PAGE);
await page.setCookie({name: 'page1cookie', value: 'page1value'});
await anotherPage.setCookie({name: 'page2cookie', value: 'page2value'});
const cookies1 = await page.cookies();
const cookies2 = await anotherPage.cookies();
expect(cookies1.length).toBe(1);
expect(cookies2.length).toBe(1);
expect(cookies1[0].name).toBe('page1cookie');
expect(cookies1[0].value).toBe('page1value');
expect(cookies2[0].name).toBe('page2cookie');
expect(cookies2[0].value).toBe('page2value');
await anotherContext.close();
});
it('should set multiple cookies', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setCookie({
name: 'password',
value: '123456'
}, {
name: 'foo',
value: 'bar'
});
expect(await page.evaluate(() => {
const cookies = document.cookie.split(';');
return cookies.map(cookie => cookie.trim()).sort();
})).toEqual([
'foo=bar',
'password=123456',
]);
});
it('should have |expires| set to |-1| for session cookies', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setCookie({
name: 'password',
value: '123456'
});
const cookies = await page.cookies();
expect(cookies[0].session).toBe(true);
expect(cookies[0].expires).toBe(-1);
});
it('should set cookie with reasonable defaults', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setCookie({ await page.setCookie({
name: 'password', name: 'password',
value: '123456' value: '123456'
}); });
expect(await page.evaluate('document.cookie')).toBe('username=John Doe; password=123456');
const cookies = await page.cookies(); const cookies = await page.cookies();
expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([{ expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([{
name: 'password', name: 'password',
@ -53,19 +187,8 @@ module.exports.addTests = function({testRunner, expect}) {
httpOnly: false, httpOnly: false,
secure: false, secure: false,
session: true session: true
}, {
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
size: 16,
httpOnly: false,
secure: false,
session: true
}]); }]);
}); });
it('should set a cookie with a path', async({page, server}) => { it('should set a cookie with a path', async({page, server}) => {
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.setCookie({ await page.setCookie({
@ -85,46 +208,25 @@ module.exports.addTests = function({testRunner, expect}) {
session: true session: true
}]); }]);
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
await page.goto(server.PREFIX + '/empty.html'); await page.goto(server.EMPTY_PAGE);
expect(await page.cookies()).toEqual([]); expect(await page.cookies()).toEqual([]);
expect(await page.evaluate('document.cookie')).toBe(''); expect(await page.evaluate('document.cookie')).toBe('');
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
}); });
it('should delete a cookie', async({page, server}) => {
await page.goto(server.PREFIX + '/grid.html');
await page.setCookie({
name: 'cookie1',
value: '1'
}, {
name: 'cookie2',
value: '2'
}, {
name: 'cookie3',
value: '3'
});
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2; cookie3=3');
await page.deleteCookie({name: 'cookie2'});
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie3=3');
});
it('should not set a cookie on a blank page', async function({page}) { it('should not set a cookie on a blank page', async function({page}) {
let error = null;
await page.goto('about:blank'); await page.goto('about:blank');
let error = null;
try { try {
await page.setCookie({name: 'example-cookie', value: 'best'}); await page.setCookie({name: 'example-cookie', value: 'best'});
} catch (e) { } catch (e) {
error = e; error = e;
} }
expect(error).toBeTruthy(); expect(error.message).toContain('At least one of the url and domain needs to be specified');
expect(error.message).toEqual('Protocol error (Network.deleteCookies): At least one of the url and domain needs to be specified');
}); });
it('should not set a cookie with blank page URL', async function({page, server}) { it('should not set a cookie with blank page URL', async function({page, server}) {
let error = null; let error = null;
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.EMPTY_PAGE);
try { try {
await page.setCookie( await page.setCookie(
{name: 'example-cookie', value: 'best'}, {name: 'example-cookie', value: 'best'},
@ -133,12 +235,10 @@ module.exports.addTests = function({testRunner, expect}) {
} catch (e) { } catch (e) {
error = e; error = e;
} }
expect(error).toBeTruthy();
expect(error.message).toEqual( expect(error.message).toEqual(
`Blank page can not have cookie "example-cookie-blank"` `Blank page can not have cookie "example-cookie-blank"`
); );
}); });
it('should not set a cookie on a data URL page', async function({page}) { it('should not set a cookie on a data URL page', async function({page}) {
let error = null; let error = null;
await page.goto('data:,Hello%2C%20World!'); await page.goto('data:,Hello%2C%20World!');
@ -147,15 +247,37 @@ module.exports.addTests = function({testRunner, expect}) {
} catch (e) { } catch (e) {
error = e; error = e;
} }
expect(error).toBeTruthy(); expect(error.message).toContain('At least one of the url and domain needs to be specified');
expect(error.message).toEqual( });
'Protocol error (Network.deleteCookies): At least one of the url and domain needs to be specified' it('should default to setting secure cookie for HTTPS websites', async({page, server}) => {
); await page.goto(server.EMPTY_PAGE);
const SECURE_URL = 'https://example.com';
await page.setCookie({
url: SECURE_URL,
name: 'foo',
value: 'bar',
});
const [cookie] = await page.cookies(SECURE_URL);
expect(cookie.secure).toBe(true);
});
it('should be able to set unsecure cookie for HTTPS website', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const SECURE_URL = 'http://example.com';
await page.setCookie({
url: SECURE_URL,
name: 'foo',
value: 'bar',
});
const [cookie] = await page.cookies(SECURE_URL);
expect(cookie.secure).toBe(false);
}); });
it('should set a cookie on a different domain', async({page, server}) => { it('should set a cookie on a different domain', async({page, server}) => {
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.EMPTY_PAGE);
await page.setCookie({name: 'example-cookie', value: 'best', url: 'https://www.example.com'}); await page.setCookie({
url: 'https://www.example.com',
name: 'example-cookie',
value: 'best',
});
expect(await page.evaluate('document.cookie')).toBe(''); expect(await page.evaluate('document.cookie')).toBe('');
expect(await page.cookies()).toEqual([]); expect(await page.cookies()).toEqual([]);
expect(await page.cookies('https://www.example.com')).toEqual([{ expect(await page.cookies('https://www.example.com')).toEqual([{
@ -170,7 +292,6 @@ module.exports.addTests = function({testRunner, expect}) {
session: true session: true
}]); }]);
}); });
it('should set cookies from a frame', async({page, server}) => { it('should set cookies from a frame', async({page, server}) => {
await page.goto(server.PREFIX + '/grid.html'); await page.goto(server.PREFIX + '/grid.html');
await page.setCookie({name: 'localhost-cookie', value: 'best'}); await page.setCookie({name: 'localhost-cookie', value: 'best'});
@ -210,7 +331,25 @@ module.exports.addTests = function({testRunner, expect}) {
secure: false, secure: false,
session: true session: true
}]); }]);
});
});
describe('Page.deleteCookie', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setCookie({
name: 'cookie1',
value: '1'
}, {
name: 'cookie2',
value: '2'
}, {
name: 'cookie3',
value: '3'
});
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2; cookie3=3');
await page.deleteCookie({name: 'cookie2'});
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie3=3');
}); });
}); });
}; };

View File

@ -236,6 +236,17 @@ module.exports.addTests = function({testRunner, expect, CHROME}) {
}); });
}); });
describe('Response.statusText', function() {
it('should work', async({page, server}) => {
server.setRoute('/cool', (req, res) => {
res.writeHead(200, 'cool!');
res.end();
});
const response = await page.goto(server.PREFIX + '/cool');
expect(response.statusText()).toBe('cool!');
});
});
describe('Network Events', function() { describe('Network Events', function() {
it('Page.Events.Request', async({page, server}) => { it('Page.Events.Request', async({page, server}) => {
const requests = []; const requests = [];
@ -264,15 +275,6 @@ module.exports.addTests = function({testRunner, expect, CHROME}) {
expect(remoteAddress.port).toBe(server.PORT); expect(remoteAddress.port).toBe(server.PORT);
}); });
it('Response.statusText', async({page, server}) => {
server.setRoute('/cool', (req, res) => {
res.writeHead(200, 'cool!');
res.end();
});
const response = await page.goto(server.PREFIX + '/cool');
expect(response.statusText()).toBe('cool!');
});
it('Page.Events.RequestFailed', async({page, server}) => { it('Page.Events.RequestFailed', async({page, server}) => {
await page.setRequestInterception(true); await page.setRequestInterception(true);
page.on('request', request => { page.on('request', request => {
@ -422,7 +424,7 @@ module.exports.addTests = function({testRunner, expect, CHROME}) {
expect(requests[1].url()).toContain('/one-style.css'); expect(requests[1].url()).toContain('/one-style.css');
expect(requests[1].headers().referer).toContain('/one-style.html'); expect(requests[1].headers().referer).toContain('/one-style.html');
}); });
it_fails_ffox('should properly return navigation response when URL has cookies', async({page, server}) => { it('should properly return navigation response when URL has cookies', async({page, server}) => {
// Setup cookie. // Setup cookie.
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setCookie({ name: 'foo', value: 'bar'}); await page.setCookie({ name: 'foo', value: 'bar'});