Skip to content

Commit 02f4079

Browse files
committed
[js] For consistency with getCookie(s), addCookie now expects the expiry to be
specified in seconds since epoch, not milliseconds. Also taking this opportunity to change addCookie to take the cookie spec as an object literal instead of a bazillion parameters (which is awkward when only the name, value, and expiry were provided). addCookie will throw a TypeError if it detects an invalid spec object. Fixes #2245
1 parent 022644c commit 02f4079

File tree

3 files changed

+147
-67
lines changed

3 files changed

+147
-67
lines changed

javascript/node/selenium-webdriver/CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
* Removed the mandatory use of Firefox Dev Edition, when using Marionette driver
1111
* Fixed timeouts' URL
1212
* `promise.Deferred` is no longer a thenable object.
13+
* `Options#addCookie()` now takes a record object instead of 7 individual
14+
parameters. A TypeError will be thrown if addCookie() is called with invalid
15+
arguments.
16+
* When adding cookies, the desired expiry must be provided as a Date or in
17+
_seconds_ since epoch. When retrieving cookies, the expiration is always
18+
returned in seconds.
1319
* Removed deprecated modules:
1420
- `selenium-webdriver/error` (use `selenium-webdriver/lib/error`,\
1521
or the `error` property exported by `selenium-webdriver`)

javascript/node/selenium-webdriver/lib/webdriver.js

Lines changed: 121 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,11 +1043,7 @@ class Navigation {
10431043
* Provides methods for managing browser and driver state.
10441044
*
10451045
* This class should never be instantiated directly. Insead, obtain an instance
1046-
* with
1047-
*
1048-
* webdriver.manage()
1049-
*
1050-
* @see WebDriver#manage()
1046+
* with {@linkplain WebDriver#manage() webdriver.manage()}.
10511047
*/
10521048
class Options {
10531049
/**
@@ -1061,57 +1057,72 @@ class Options {
10611057

10621058
/**
10631059
* Schedules a command to add a cookie.
1064-
* @param {string} name The cookie name.
1065-
* @param {string} value The cookie value.
1066-
* @param {string=} opt_path The cookie path.
1067-
* @param {string=} opt_domain The cookie domain.
1068-
* @param {boolean=} opt_isSecure Whether the cookie is secure.
1069-
* @param {(number|!Date)=} opt_expiry When the cookie expires. If specified
1070-
* as a number, should be in milliseconds since midnight,
1071-
* January 1, 1970 UTC.
1060+
*
1061+
* __Sample Usage:__
1062+
*
1063+
* // Set a basic cookie.
1064+
* driver.options().addCookie({name: 'foo', value: 'bar'});
1065+
*
1066+
* // Set a cookie that expires in 10 minutes.
1067+
* let expiry = new Date(Date.now() + (10 * 60 * 1000));
1068+
* driver.options().addCookie({name: 'foo', value: 'bar', expiry});
1069+
*
1070+
* // The cookie expiration may also be specified in seconds since epoch.
1071+
* driver.options().addCookie({
1072+
* name: 'foo',
1073+
* value: 'bar',
1074+
* expiry: Math.floor(Date.now() / 1000)
1075+
* });
1076+
*
1077+
* @param {!Options.Cookie} spec Defines the cookie to add.
10721078
* @return {!promise.Promise<void>} A promise that will be resolved
10731079
* when the cookie has been added to the page.
1080+
* @throws {error.InvalidArgumentError} if any of the cookie parameters are
1081+
* invalid.
1082+
* @throws {TypeError} if `spec` is not a cookie object.
10741083
*/
1075-
addCookie(name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {
1084+
addCookie(spec) {
1085+
if (!spec || typeof spec !== 'object') {
1086+
throw TypeError('addCookie called with non-cookie parameter');
1087+
}
1088+
10761089
// We do not allow '=' or ';' in the name.
1090+
let name = spec.name;
10771091
if (/[;=]/.test(name)) {
10781092
throw new error.InvalidArgumentError(
10791093
'Invalid cookie name "' + name + '"');
10801094
}
10811095

10821096
// We do not allow ';' in value.
1097+
let value = spec.value;
10831098
if (/;/.test(value)) {
10841099
throw new error.InvalidArgumentError(
10851100
'Invalid cookie value "' + value + '"');
10861101
}
10871102

1088-
var cookieString = name + '=' + value +
1089-
(opt_domain ? ';domain=' + opt_domain : '') +
1090-
(opt_path ? ';path=' + opt_path : '') +
1091-
(opt_isSecure ? ';secure' : '');
1092-
1093-
var expiry;
1094-
if (opt_expiry !== void(0)) {
1095-
var expiryDate;
1096-
if (typeof opt_expiry === 'number') {
1097-
expiryDate = new Date(opt_expiry);
1098-
} else {
1099-
expiryDate = /** @type {!Date} */ (opt_expiry);
1100-
opt_expiry = expiryDate.getTime();
1101-
}
1102-
cookieString += ';expires=' + expiryDate.toUTCString();
1103-
// Convert from milliseconds to seconds.
1104-
expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);
1103+
let cookieString = name + '=' + value +
1104+
(spec.domain ? ';domain=' + spec.domain : '') +
1105+
(spec.path ? ';path=' + spec.path : '') +
1106+
(spec.secure ? ';secure' : '');
1107+
1108+
let expiry;
1109+
if (typeof spec.expiry === 'number') {
1110+
spec.expiry = Math.floor(spec.expiry);
1111+
cookieString += ';expires=' + new Date(spec.expiry * 1000).toUTCString();
1112+
} else if (spec.expiry instanceof Date) {
1113+
let date = /** @type {!Date} */(spec.expiry);
1114+
expiry = Math.floor(date.getTime() / 1000);
1115+
cookieString += ';expires=' + date.toUTCString();
11051116
}
11061117

11071118
return this.driver_.schedule(
11081119
new command.Command(command.Name.ADD_COOKIE).
11091120
setParameter('cookie', {
11101121
'name': name,
11111122
'value': value,
1112-
'path': opt_path,
1113-
'domain': opt_domain,
1114-
'secure': !!opt_isSecure,
1123+
'path': spec.path,
1124+
'domain': spec.domain,
1125+
'secure': !!spec.secure,
11151126
'expiry': expiry
11161127
}),
11171128
'WebDriver.manage().addCookie(' + cookieString + ')');
@@ -1129,8 +1140,8 @@ class Options {
11291140
}
11301141

11311142
/**
1132-
* Schedules a command to delete the cookie with the given name. This command is
1133-
* a no-op if there is no cookie with the given name visible to the current
1143+
* Schedules a command to delete the cookie with the given name. This command
1144+
* is a no-op if there is no cookie with the given name visible to the current
11341145
* page.
11351146
* @param {string} name The name of the cookie to delete.
11361147
* @return {!promise.Promise<void>} A promise that will be resolved
@@ -1147,8 +1158,8 @@ class Options {
11471158
* Schedules a command to retrieve all cookies visible to the current page.
11481159
* Each cookie will be returned as a JSON object as described by the WebDriver
11491160
* wire protocol.
1150-
* @return {!promise.Promise<!Array<WebDriver.Options.Cookie>>} A
1151-
* promise that will be resolved with the cookies visible to the current page.
1161+
* @return {!promise.Promise<!Array<!Options.Cookie>>} A promise that will be
1162+
* resolved with the cookies visible to the current browsing context.
11521163
*/
11531164
getCookies() {
11541165
return this.driver_.schedule(
@@ -1162,9 +1173,8 @@ class Options {
11621173
* described by the WebDriver wire protocol.
11631174
*
11641175
* @param {string} name The name of the cookie to retrieve.
1165-
* @return {!promise.Promise<?WebDriver.Options.Cookie>} A promise
1166-
* that will be resolved with the named cookie, or `null` if there is no
1167-
* such cookie.
1176+
* @return {!promise.Promise<?Options.Cookie>} A promise that will be resolved
1177+
* with the named cookie, or `null` if there is no such cookie.
11681178
*/
11691179
getCookie(name) {
11701180
return this.getCookies().then(function(cookies) {
@@ -1202,17 +1212,77 @@ class Options {
12021212

12031213

12041214
/**
1205-
* A JSON description of a browser cookie.
1206-
* @typedef {{
1207-
* name: string,
1208-
* value: string,
1209-
* path: (string|undefined),
1210-
* domain: (string|undefined),
1211-
* secure: (boolean|undefined),
1212-
* expiry: (number|undefined)
1213-
* }}
1215+
* A record object describing a browser cookie.
1216+
*
1217+
* @record
1218+
*/
1219+
Options.Cookie = function() {};
1220+
1221+
1222+
/**
1223+
* The name of the cookie.
1224+
*
1225+
* @type {string}
1226+
*/
1227+
Options.Cookie.prototype.name;
1228+
1229+
1230+
/**
1231+
* The cookie value.
1232+
*
1233+
* @type {string}
1234+
*/
1235+
Options.Cookie.prototype.value;
1236+
1237+
1238+
/**
1239+
* The cookie path. Defaults to "/" when adding a cookie.
1240+
*
1241+
* @type {(string|undefined)}
1242+
*/
1243+
Options.Cookie.prototype.path;
1244+
1245+
1246+
/**
1247+
* The domain the cookie is visible to. Defaults to the current browsing
1248+
* context's document's URL when adding a cookie.
1249+
*
1250+
* @type {(string|undefined)}
1251+
*/
1252+
Options.Cookie.prototype.domain;
1253+
1254+
1255+
/**
1256+
* Whether the cookie is a secure cookie. Defaults to false when adding a new
1257+
* cookie.
1258+
*
1259+
* @type {(boolean|undefined)}
1260+
*/
1261+
Options.Cookie.prototype.secure;
1262+
1263+
1264+
/**
1265+
* Whether the cookie is an HTTP only cookie. Defaults to false when adding a
1266+
* new cookie.
1267+
*
1268+
* @type {(boolean|undefined)}
1269+
*/
1270+
Options.Cookie.prototype.httpOnly;
1271+
1272+
1273+
/**
1274+
* When the cookie expires.
1275+
*
1276+
* When {@linkplain Options#addCookie() adding a cookie}, this may be specified
1277+
* in _seconds_ since Unix epoch (January 1, 1970). The expiry will default to
1278+
* 20 years in the future if omitted.
1279+
*
1280+
* The expiry is always returned in seconds since epoch when
1281+
* {@linkplain Options#getCookies() retrieving cookies} from the browser.
1282+
*
1283+
* @type {(!Date|number|undefined)}
12141284
*/
1215-
Options.Cookie;
1285+
Options.Cookie.prototype.expiry;
12161286

12171287

12181288
/**

javascript/node/selenium-webdriver/test/cookie_test.js

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ test.suite(function(env) {
4949
test.it('can add new cookies', function() {
5050
var cookie = createCookieSpec();
5151

52-
driver.manage().addCookie(cookie.name, cookie.value);
52+
driver.manage().addCookie(cookie);
5353
driver.manage().getCookie(cookie.name).then(function(actual) {
5454
assert.equal(actual.value, cookie.value);
5555
});
@@ -59,23 +59,24 @@ test.suite(function(env) {
5959
var cookie1 = createCookieSpec();
6060
var cookie2 = createCookieSpec();
6161

62-
driver.manage().addCookie(cookie1.name, cookie1.value);
63-
driver.manage().addCookie(cookie2.name, cookie2.value);
62+
driver.manage().addCookie(cookie1);
63+
driver.manage().addCookie(cookie2);
6464

6565
assertHasCookies(cookie1, cookie2);
6666
});
6767

6868
test.ignore(env.browsers(Browser.IE)).
6969
it('only returns cookies visible to the current page', function() {
7070
var cookie1 = createCookieSpec();
71-
var cookie2 = createCookieSpec();
7271

73-
driver.manage().addCookie(cookie1.name, cookie1.value);
72+
driver.manage().addCookie(cookie1);
7473

7574
var pageUrl = fileserver.whereIs('page/1');
75+
var cookie2 = createCookieSpec({
76+
path: url.parse(pageUrl).pathname
77+
});
7678
driver.get(pageUrl);
77-
driver.manage().addCookie(
78-
cookie2.name, cookie2.value, url.parse(pageUrl).pathname);
79+
driver.manage().addCookie(cookie2);
7980
assertHasCookies(cookie1, cookie2);
8081

8182
driver.get(fileserver.Pages.ajaxyPage);
@@ -137,7 +138,7 @@ test.suite(function(env) {
137138
'child/grandchild/grandchildPage.html');
138139

139140
driver.get(childUrl);
140-
driver.manage().addCookie(cookie.name, cookie.value);
141+
driver.manage().addCookie(cookie);
141142
assertHasCookies(cookie);
142143

143144
driver.get(grandchildUrl);
@@ -152,28 +153,31 @@ test.suite(function(env) {
152153

153154
test.ignore(env.browsers(Browser.ANDROID, Browser.FIREFOX, Browser.IE)).
154155
it('should retain cookie expiry', function() {
155-
var cookie = createCookieSpec();
156-
var expirationDelay = 5 * 1000;
157-
var futureTime = Date.now() + expirationDelay;
156+
let expirationDelay = 5 * 1000;
157+
let expiry = new Date(Date.now() + expirationDelay);
158+
let cookie = createCookieSpec({expiry});
158159

159-
driver.manage().addCookie(
160-
cookie.name, cookie.value, null, null, false, futureTime);
160+
driver.manage().addCookie(cookie);
161161
driver.manage().getCookie(cookie.name).then(function(actual) {
162162
assert.equal(actual.value, cookie.value);
163163
// expiry times are exchanged in seconds since January 1, 1970 UTC.
164-
assert.equal(actual.expiry, Math.floor(futureTime / 1000));
164+
assert.equal(actual.expiry, Math.floor(expiry.getTime() / 1000));
165165
});
166166

167167
driver.sleep(expirationDelay);
168168
assertHasCookies();
169169
});
170170
});
171171

172-
function createCookieSpec() {
173-
return {
172+
function createCookieSpec(opt_options) {
173+
let spec = {
174174
name: getRandomString(),
175175
value: getRandomString()
176176
};
177+
if (opt_options) {
178+
spec = Object.assign(spec, opt_options);
179+
}
180+
return spec;
177181
}
178182

179183
function buildCookieMap(cookies) {

0 commit comments

Comments
 (0)