safari.js

// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * @fileoverview Defines a WebDriver client for Safari.
 *
 * @module selenium-webdriver/safari
 */

'use strict'

const http = require('./http')
const remote = require('./remote')
const webdriver = require('./lib/webdriver')
const { Browser, Capabilities } = require('./lib/capabilities')
const { getBinaryPaths } = require('./common/driverFinder')

/**
 * Creates {@link remote.DriverService} instances that manage
 * a [safaridriver] server in a child process.
 *
 * [safaridriver]: https://developer.apple.com/library/prerelease/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_10_0.html#//apple_ref/doc/uid/TP40014305-CH11-DontLinkElementID_28
 */
class ServiceBuilder extends remote.DriverService.Builder {
  /**
   * @param {string=} opt_exe Path to the server executable to use. If omitted,
   *     the builder will attempt to locate the safaridriver on the system PATH.
   */
  constructor(opt_exe) {
    super(opt_exe)
    this.setLoopback(true) // Required.
  }
}

const OPTIONS_CAPABILITY_KEY = 'safari:options'
const TECHNOLOGY_PREVIEW_OPTIONS_KEY = 'technologyPreview'

/**
 * Configuration options specific to the {@link Driver SafariDriver}.
 */
class Options extends Capabilities {
  /**
   * @param {(Capabilities|Map<string, ?>|Object)=} other Another set of
   *     capabilities to initialize this instance from.
   */
  constructor(other = undefined) {
    super(other)

    /** @private {!Object} */
    this.options_ = this.get(OPTIONS_CAPABILITY_KEY) || {}

    this.set(OPTIONS_CAPABILITY_KEY, this.options_)
    this.setBrowserName(Browser.SAFARI)
  }

  /**
   * Instruct the SafariDriver to use the Safari Technology Preview if true.
   * Otherwise, use the release version of Safari. Defaults to using the release version of Safari.
   *
   * @param {boolean} useTechnologyPreview
   * @return {!Options} A self reference.
   */
  setTechnologyPreview(useTechnologyPreview) {
    this.options_[TECHNOLOGY_PREVIEW_OPTIONS_KEY] = !!useTechnologyPreview
    return this
  }
}

/**
 * @param  {(Capabilities|Object<string, *>)=} o The options object
 * @return {boolean}
 */
function useTechnologyPreview(o) {
  if (o instanceof Capabilities) {
    let options = o.get(OPTIONS_CAPABILITY_KEY)
    return !!(options && options[TECHNOLOGY_PREVIEW_OPTIONS_KEY])
  }

  if (o && typeof o === 'object') {
    return !!o[TECHNOLOGY_PREVIEW_OPTIONS_KEY]
  }

  return false
}

const SAFARIDRIVER_TECHNOLOGY_PREVIEW_EXE = '/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver'

/**
 * A WebDriver client for Safari. This class should never be instantiated
 * directly; instead, use the {@linkplain ./builder.Builder Builder}:
 *
 *     var driver = new Builder()
 *         .forBrowser('safari')
 *         .build();
 *
 */
class Driver extends webdriver.WebDriver {
  /**
   * Creates a new Safari session.
   *
   * @param {(Options|Capabilities)=} options The configuration options.
   * @return {!Driver} A new driver instance.
   */
  static createSession(options) {
    let caps = options || new Options()

    let exe
    if (useTechnologyPreview(caps.get(OPTIONS_CAPABILITY_KEY))) {
      exe = SAFARIDRIVER_TECHNOLOGY_PREVIEW_EXE
    }

    let service = new ServiceBuilder(exe).build()
    if (!service.getExecutable()) {
      service.setExecutable(getBinaryPaths(caps).driverPath)
    }
    let executor = new http.Executor(service.start().then((url) => new http.HttpClient(url)))

    return /** @type {!Driver} */ (super.createSession(executor, caps, () => service.kill()))
  }
}

// Public API

exports.Driver = Driver
exports.Options = Options
exports.ServiceBuilder = ServiceBuilder