[Selenium] WebDriverでaddCookieするとき単位はミリ秒にしないといけない


躓きポイント

取ってきたCookieを削除してもう1回すぐセットするだけのコードを書いたつもりが、何故か2回目のセットで失敗してしまう。ところがexpiryだけ無視すると正常にセットされた。Cookieオブジェクトでは秒単位なのに、addCookieの引数はミリ秒単位になっているという意味不明な実装が原因だった。

Cookieをオブジェクトとして取得

var cookie = await driver.manage().getCookie('PHPSESSID');

Cookieの削除

await driver.manage().deleteCookie('PHPSESSID');

Cookieの再セット

BAD
await driver.manage().addCookie(
    cookie.name,
    cookie.value,
    cookie.path,
    cookie.domain,
    cookie.secure,
    cookie.expiry
);
GOOD
await driver.manage().addCookie(
    cookie.name,
    cookie.value,
    cookie.path,
    cookie.domain,
    cookie.secure,
    cookie.expiry * 1000
);

当該ソースを読んでみる

ググっても全然同様の質問が見当たらなかったので自分でソース読んで見るまで気づきませんでした。何でこうなってるんだろう…

webdriver.WebDriver.Options.prototype.addCookie = function(
    name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {
  // We do not allow '=' or ';' in the name.
  if (/[;=]/.test(name)) {
    throw Error('Invalid cookie name "' + name + '"');
  }

  // We do not allow ';' in value.
  if (/;/.test(value)) {
    throw Error('Invalid cookie value "' + value + '"');
  }

  var cookieString = name + '=' + value +
      (opt_domain ? ';domain=' + opt_domain : '') +
      (opt_path ? ';path=' + opt_path : '') +
      (opt_isSecure ? ';secure' : '');

  var expiry;
  if (goog.isDef(opt_expiry)) {
    var expiryDate;
    if (goog.isNumber(opt_expiry)) {
      expiryDate = new Date(opt_expiry);
    } else {
      expiryDate = /** @type {!Date} */ (opt_expiry);
      opt_expiry = expiryDate.getTime();
    }
    cookieString += ';expires=' + expiryDate.toUTCString();
    // Convert from milliseconds to seconds.
    expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);
  }

  return this.driver_.schedule(
      new webdriver.Command(webdriver.CommandName.ADD_COOKIE).
          setParameter('cookie', {
            'name': name,
            'value': value,
            'path': opt_path,
            'domain': opt_domain,
            'secure': !!opt_isSecure,
            'expiry': expiry
          }),
      'WebDriver.manage().addCookie(' + cookieString + ')');
};

Node.jsだけでなく、Java本家もそうなってました。うーん…

public class AddCookie extends WebDriverHandler<Void> implements JsonParametersAware {

  private volatile Map<String, Object> rawCookie;

  public AddCookie(Session session) {
    super(session);
  }

  @Override
  public Void call() throws Exception {
    Cookie cookie = createCookie();

    getDriver().manage().addCookie(cookie);

    return null;
  }

  @SuppressWarnings({"unchecked"})
  public void setJsonParameters(Map<String, Object> allParameters) throws Exception {
    if (allParameters == null) {
      return;
    }
    rawCookie = Maps.newHashMap((Map<String, Object>) allParameters.get("cookie"));
  }

  protected Cookie createCookie() {
    if (rawCookie == null) {
      return null;
    }

    String name = (String) rawCookie.get("name");
    String value = (String) rawCookie.get("value");
    String path = (String) rawCookie.get("path");
    String domain = (String) rawCookie.get("domain");
    Boolean secure = (Boolean) rawCookie.get("secure");
    if (secure == null) {
        secure = false;
    }

    Number expiryNum = (Number) rawCookie.get("expiry");
    Date expiry = expiryNum == null ? null : new Date(
        TimeUnit.SECONDS.toMillis(expiryNum.longValue()));

    return new Cookie.Builder(name, value)
        .path(path)
        .domain(domain)
        .isSecure(secure)
        .expiresOn(expiry)
        .build();
  }

  @Override
  public String toString() {
    return "[add cookie: " + createCookie() + "]";
  }
}