rubycdp / cuprite

Headless Chrome/Chromium driver for Capybara
https://cuprite.rubycdp.com
MIT License
1.25k stars 92 forks source link

Capybara::Cuprite::DeadBrowser with a new version of Chrome 73 #72

Closed vermaxik closed 5 years ago

vermaxik commented 5 years ago

Hallo,

With a new version of Google Chrome, I have the same issue as here https://github.com/machinio/cuprite/issues/48

Environment:

` Got 0 failures and 3 other errors:

      Capybara::Cuprite::DeadBrowser:
        Chrome is dead
      # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:41:in `wait'
      # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:196:in `command'
      # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:77:in `visit'
      # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:31:in `visit'
      # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/session.rb:274:in `visit'`
route commented 5 years ago

Works just fine with new browser on my machine. Could you please run tests with CUPRITE_DEBUG=true?

vermaxik commented 5 years ago

I'm using docker contrainer with no-sandbox options

    <<< {"id":1,"result":{"targetInfos":[{"targetId":"43F15505150DE6A6F27673655FCFC028","type":"page","title":"","url":"about:blank","attached":false,"browserContextId":"614D5183645E2B4628ED820405AA995E"}]}}

>>> {"method":"Target.createBrowserContext","params":{},"id":2}
    <<< {"id":2,"result":{"browserContextId":"1350334B7EF8A4625FE7E5A7937DBDB2"}}

>>> {"method":"Target.createTarget","params":{"url":"about:blank","browserContextId":"1350334B7EF8A4625FE7E5A7937DBDB2"},"id":3}
    <<< {"id":3,"result":{"targetId":"F8F11DFDF7A67282EBC76EB6DAB06B46"}}

>>> {"method":"Target.attachToTarget","params":{"targetId":"F8F11DFDF7A67282EBC76EB6DAB06B46"},"id":4}
    <<< {"method":"Target.attachedToTarget","params":{"sessionId":"A63DAC7C5D613751CB0AB0B3EF9D7C59","targetInfo":{"targetId":"F8F11DFDF7A67282EBC76EB6DAB06B46","type":"page","title":"","url":"about:blank","attached":true,"browserContextId":"1350334B7EF8A4625FE7E5A7937DBDB2"},"waitingForDebugger":false}}
    <<< {"id":4,"result":{"sessionId":"A63DAC7C5D613751CB0AB0B3EF9D7C59"}}

>>> {"method":"Page.enable","params":{},"id":1}
    <<< {"id":1,"result":{}}

>>> {"method":"DOM.enable","params":{},"id":2}
    <<< {"id":2,"result":{}}

>>> {"method":"CSS.enable","params":{},"id":3}
    <<< {"id":3,"result":{}}

>>> {"method":"Runtime.enable","params":{},"id":4}
    <<< {"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"origin":"://","name":"","auxData":{"isDefault":true,"type":"default","frameId":"F8F11DFDF7A67282EBC76EB6DAB06B46"}}}}
    <<< {"id":4,"result":{}}

>>> {"method":"Log.enable","params":{},"id":5}
    <<< {"id":5,"result":{}}

>>> {"method":"Network.enable","params":{},"id":6}
    <<< {"id":6,"result":{}}

>>> {"method":"Page.setDownloadBehavior","params":{"behavior":"allow","downloadPath":"/app/capybara"},"id":7}
    <<< {"id":7,"result":{}}

>>> {"method":"Page.addScriptToEvaluateOnNewDocument","params":{"source":"class InvalidSelector extends Error {}\nclass TimedOutPromise extends Error {}\nclass MouseEventFailed extends Error {}\n\nconst EVENTS = {\n  FOCUS: [\"blur\", \"focus\", \"focusin\", \"focusout\"],\n  MOUSE: [\"click\", \"dblclick\", \"mousedown\", \"mouseenter\", \"mouseleave\",\n          \"mousemove\", \"mouseover\", \"mouseout\", \"mouseup\", \"contextmenu\"],\n  FORM: [\"submit\"]\n}\n\nclass Cuprite {\n  constructor() {\n    this._json = JSON; // In case someone overrides it like mootools\n  }\n\n  find(method, selector, within = document) {\n    try {\n      let results = [];\n\n      if (method == \"xpath\") {\n        let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n        for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n          results.push(xpath.snapshotItem(i));\n        }\n      } else {\n        results = Array.from(within.querySelectorAll(selector));\n      }\n\n      return results;\n    } catch (error) {\n      // DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code\n      if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {\n        throw new InvalidSelector;\n      } else {\n        throw error;\n      }\n    }\n  }\n\n  parents(node) {\n    let nodes = [];\n    let parent = node.parentNode;\n    while (parent != document) {\n      nodes.push(parent);\n      parent = parent.parentNode;\n    }\n    return nodes;\n  }\n\n  visibleText(node) {\n    if (this.isVisible(node)) {\n      if (node.nodeName == \"TEXTAREA\") {\n        return node.textContent;\n      } else {\n        if (node instanceof SVGElement) {\n          return node.textContent;\n        } else {\n          return node.innerText;\n        }\n      }\n    }\n  }\n\n  isVisible(node) {\n    let mapName, style;\n    // if node is area, check visibility of relevant image\n    if (node.tagName === \"AREA\") {\n      mapName = document.evaluate(\"./ancestor::map/@name\", node, null, XPathResult.STRING_TYPE, null).stringValue;\n      node = document.querySelector(`img[usemap=\"#${mapName}\"]`);\n      if (node == null) {\n        return false;\n      }\n    }\n\n    while (node) {\n      style = window.getComputedStyle(node);\n      if (style.display === \"none\" || style.visibility === \"hidden\" || parseFloat(style.opacity) === 0) {\n        return false;\n      }\n      node = node.parentElement;\n    }\n\n    return true;\n  }\n\n\n  isDisabled(node) {\n    let xpath = \"parent::optgroup[@disabled] | \\\n                 ancestor::select[@disabled] | \\\n                 parent::fieldset[@disabled] | \\\n                 ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]\";\n\n    return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;\n  }\n\n  path(node) {\n    let nodes = [node];\n    let parent = node.parentNode;\n    while (parent !== document) {\n      nodes.unshift(parent);\n      parent = parent.parentNode;\n    }\n\n    let selectors = nodes.map(node =\u003e {\n      let prevSiblings = [];\n      let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n\n      for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n        prevSiblings.push(xpath.snapshotItem(i));\n      }\n\n      return `${node.tagName}[${(prevSiblings.length + 1)}]`;\n    });\n\n    return `//${selectors.join(\"/\")}`;\n  }\n\n  set(node, value) {\n    if (node.readOnly) return;\n\n    if (node.maxLength \u003e= 0) {\n      value = value.substr(0, node.maxLength);\n    }\n\n    this.trigger(node, \"focus\");\n    this.setValue(node, \"\");\n\n    if (node.type == \"number\" || node.type == \"date\") {\n      this.setValue(node, value);\n    } else if (node.type == \"time\") {\n      this.setValue(node, new Date(value).toTimeString().split(\" \")[0]);\n    } else if (node.type == \"datetime-local\") {\n      value = new Date(value);\n      let year = value.getFullYear();\n      let month = (\"0\" + (value.getMonth() + 1)).slice(-2);\n      let date = (\"0\" + value.getDate()).slice(-2);\n      let hour = (\"0\" + value.getHours()).slice(-2);\n      let min = (\"0\" + value.getMinutes()).slice(-2);\n      let sec = (\"0\" + value.getSeconds()).slice(-2);\n      this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);\n    } else {\n      for (let i = 0; i \u003c value.length; i++) {\n        let char = value[i];\n        let keyCode = this.characterToKeyCode(char);\n        this.keyupdowned(node, \"keydown\", keyCode);\n        this.setValue(node, node.value + char);\n\n        this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));\n        this.keyupdowned(node, \"keyup\", keyCode);\n      }\n    }\n\n    this.changed(node);\n    this.input(node);\n    this.trigger(node, \"blur\");\n  }\n\n  setValue(node, value) {\n    let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set;\n    let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, \"value\").set;\n\n    if (node.tagName.toLowerCase() === 'input') {\n      return nativeInputValueSetter.call(node, value);\n    }\n    return nativeTextareaValueSetter.call(node, value);\n  }\n\n  input(node) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"input\", true, false);\n    node.dispatchEvent(event);\n  }\n\n  keyupdowned(node, eventName, keyCode) {\n    let event = document.createEvent(\"UIEvents\");\n    event.initEvent(eventName, true, true);\n    event.keyCode  = keyCode;\n    event.charCode = 0;\n    node.dispatchEvent(event);\n  }\n\n  keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {\n    event = document.createEvent(\"UIEvents\");\n    event.initEvent(\"keypress\", true, true);\n    event.window   = window;\n    event.altKey   = altKey;\n    event.ctrlKey  = ctrlKey;\n    event.shiftKey = shiftKey;\n    event.metaKey  = metaKey;\n    event.keyCode  = keyCode;\n    event.charCode = charCode;\n    node.dispatchEvent(event);\n  }\n\n  characterToKeyCode(char) {\n    const specialKeys = {\n      96: 192,  // `\n      45: 189,  // -\n      61: 187,  // =\n      91: 219,  // [\n      93: 221,  // ]\n      92: 220,  // \\\n      59: 186,  // ;\n      39: 222,  // '\n      44: 188,  // ,\n      46: 190,  // .\n      47: 191,  // /\n      127: 46,  // delete\n      126: 192, // ~\n      33: 49,   // !\n      64: 50,   // @\n      35: 51,   // #\n      36: 52,   // $\n      37: 53,   // %\n      94: 54,   // ^\n      38: 55,   // \u0026\n      42: 56,   // *\n      40: 57,   // (\n      41: 48,   // )\n      95: 189,  // _\n      43: 187,  // +\n      123: 219, // {\n      125: 221, // }\n      124: 220, // |\n      58: 186,  // :\n      34: 222,  // \"\n      60: 188,  // \u003c\n      62: 190,  // \u003e\n      63: 191,  // ?\n    }\n\n    let code = char.toUpperCase().charCodeAt(0);\n    return specialKeys[code] || code;\n  }\n\n  scrollIntoViewport(node) {\n    let areaImage = this._getAreaImage(node);\n\n    if (areaImage) {\n      return this.scrollIntoViewport(areaImage);\n    } else {\n      node.scrollIntoViewIfNeeded();\n\n      if (!this._isInViewport(node)) {\n        node.scrollIntoView({block: \"center\", inline: \"center\", behavior: \"instant\"});\n        return this._isInViewport(node);\n      }\n\n      return true;\n    }\n  }\n\n  mouseEventTest(node, name, x, y) {\n    let frameOffset = this._frameOffset();\n    x -= frameOffset.left;\n    y -= frameOffset.top;\n\n    let element = document.elementFromPoint(x, y);\n\n    let el = element;\n    while (el) {\n      if (el == node) {\n        return true;\n      } else {\n        el = el.parentNode;\n      }\n    }\n\n    let selector = element \u0026\u0026 this._getSelector(element) || \"none\";\n    throw new MouseEventFailed([name, selector, x, y].join(\", \"));\n  }\n\n  _getAreaImage(node) {\n    if (\"area\" == node.tagName.toLowerCase()) {\n      let map = node.parentNode;\n      if (map.tagName.toLowerCase() != \"map\") {\n        throw new Error(\"the area is not within a map\");\n      }\n\n      let mapName = map.getAttribute(\"name\");\n      if (typeof mapName === \"undefined\" || mapName === null) {\n        throw new Error(\"area's parent map must have a name\");\n      }\n\n      mapName = `#${mapName.toLowerCase()}`;\n      let imageNode = this.find(\"css\", `img[usemap='${mapName}']`)[0];\n      if (typeof imageNode === \"undefined\" || imageNode === null) {\n        throw new Error(\"no image matches the map\");\n      }\n\n      return imageNode;\n    }\n  }\n\n  _frameOffset() {\n    let win = window;\n    let offset = { top: 0, left: 0 };\n\n    while (win.frameElement) {\n      let rect = win.frameElement.getClientRects()[0];\n      let style = win.getComputedStyle(win.frameElement);\n      win = win.parent;\n\n      offset.top += rect.top + parseInt(style.getPropertyValue(\"padding-top\"), 10)\n      offset.left += rect.left + parseInt(style.getPropertyValue(\"padding-left\"), 10)\n    }\n\n    return offset;\n  }\n\n  _getSelector(el) {\n    let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + \" \" : \"\";\n    selector += el.tagName.toLowerCase();\n    if (el.id) { selector += `#${el.id}` };\n    el.classList.forEach(c =\u003e selector += `.${c}`);\n    return selector;\n  }\n\n  _isInViewport(node) {\n    let rect = node.getBoundingClientRect();\n    return rect.top \u003e= 0 \u0026\u0026\n           rect.left \u003e= 0 \u0026\u0026\n           rect.bottom \u003c= window.innerHeight \u0026\u0026\n           rect.right \u003c= window.innerWidth;\n  }\n\n  select(node, value) {\n    if (this.isDisabled(node)) {\n      return false;\n    } else if (value == false \u0026\u0026 !node.parentNode.multiple) {\n      return false;\n    } else {\n      this.trigger(node.parentNode, \"focus\");\n\n      node.selected = value;\n      this.changed(node);\n\n      this.trigger(node.parentNode, \"blur\");\n      return true;\n    }\n  }\n\n  changed(node) {\n    let element;\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"change\", true, false);\n\n    // In the case of an OPTION tag, the change event should come\n    // from the parent SELECT\n    if (node.nodeName == \"OPTION\") {\n      element = node.parentNode\n      if (element.nodeName == \"OPTGROUP\") {\n        element = element.parentNode\n      }\n      element\n    } else {\n      element = node\n    }\n\n    element.dispatchEvent(event)\n  }\n\n  trigger(node, name, options = {}) {\n    let event;\n\n    if (EVENTS.MOUSE.indexOf(name) != -1) {\n      event = document.createEvent(\"MouseEvent\");\n      event.initMouseEvent(\n        name, true, true, window, 0,\n        options[\"screenX\"] || 0, options[\"screenY\"] || 0,\n        options[\"clientX\"] || 0, options[\"clientY\"] || 0,\n        options[\"ctrlKey\"] || false,\n        options[\"altKey\"] || false,\n        options[\"shiftKey\"] || false,\n        options[\"metaKey\"] || false,\n        options[\"button\"] || 0, null\n      )\n    } else if (EVENTS.FOCUS.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else if (EVENTS.FORM.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else {\n      throw \"Unknown event\";\n    }\n\n    node.dispatchEvent(event);\n  }\n\n  obtainEvent(name) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(name, true, true);\n    return event;\n  }\n\n  getAttributes(node) {\n    let attrs = {};\n    for (let i = 0, len = node.attributes.length; i \u003c len; i++) {\n      let attr = node.attributes[i];\n      attrs[attr.name] = attr.value.replace(\"\\n\", \"\\\\n\");\n    }\n\n    return this._json.stringify(attrs);\n  }\n\n  getAttribute(node, name) {\n    if (name == \"checked\" || name == \"selected\") {\n      return node[name];\n    } else {\n      return node.getAttribute(name);\n    }\n  }\n\n  value(node) {\n    if (node.tagName == \"SELECT\" \u0026\u0026 node.multiple) {\n      let result = []\n\n      for (let i = 0, len = node.children.length; i \u003c len; i++) {\n        let option = node.children[i];\n        if (option.selected) {\n          result.push(option.value);\n        }\n      }\n\n      return result;\n    } else {\n      return node.value;\n    }\n  }\n\n  deleteText(node) {\n    let range = document.createRange();\n    range.selectNodeContents(node);\n    window.getSelection().removeAllRanges();\n    window.getSelection().addRange(range);\n    window.getSelection().deleteFromDocument();\n  }\n\n  containsSelection(node) {\n    let selectedNode = document.getSelection().focusNode;\n\n    if (!selectedNode) {\n      return false;\n    }\n\n    if (selectedNode.nodeType == 3) {\n      selectedNode = selectedNode.parentNode;\n    }\n\n    return node.contains(selectedNode);\n  }\n\n  isCyclic(object) {\n    if (Array.isArray(object) \u0026\u0026 object.every(n =\u003e n instanceof Node)) {\n      return false;\n    }\n\n    try {\n      this._json.stringify(object);\n      return false;\n    } catch (e) {\n      return true;\n    }\n  }\n\n  // This command is purely for testing error handling\n  browserError() {\n    throw new Error(\"zomg\");\n  }\n}\n\nwindow._cuprite = new Cuprite;\n"},"id":8}

>>> {"method":"Page.addScriptToEvaluateOnNewDocument","params":{"source":"/* global $ */\ndocument.addEventListener('DOMContentLoaded', function() {\n  // kill bootstrap transitions\n  $.support.transition = false\n\n  // kill all CSS transitions\n})\n"},"id":9}

>>> {"method":"Runtime.evaluate","params":{"expression":"class InvalidSelector extends Error {}\nclass TimedOutPromise extends Error {}\nclass MouseEventFailed extends Error {}\n\nconst EVENTS = {\n  FOCUS: [\"blur\", \"focus\", \"focusin\", \"focusout\"],\n  MOUSE: [\"click\", \"dblclick\", \"mousedown\", \"mouseenter\", \"mouseleave\",\n          \"mousemove\", \"mouseover\", \"mouseout\", \"mouseup\", \"contextmenu\"],\n  FORM: [\"submit\"]\n}\n\nclass Cuprite {\n  constructor() {\n    this._json = JSON; // In case someone overrides it like mootools\n  }\n\n  find(method, selector, within = document) {\n    try {\n      let results = [];\n\n      if (method == \"xpath\") {\n        let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n        for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n          results.push(xpath.snapshotItem(i));\n        }\n      } else {\n        results = Array.from(within.querySelectorAll(selector));\n      }\n\n      return results;\n    } catch (error) {\n      // DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code\n      if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {\n        throw new InvalidSelector;\n      } else {\n        throw error;\n      }\n    }\n  }\n\n  parents(node) {\n    let nodes = [];\n    let parent = node.parentNode;\n    while (parent != document) {\n      nodes.push(parent);\n      parent = parent.parentNode;\n    }\n    return nodes;\n  }\n\n  visibleText(node) {\n    if (this.isVisible(node)) {\n      if (node.nodeName == \"TEXTAREA\") {\n        return node.textContent;\n      } else {\n        if (node instanceof SVGElement) {\n          return node.textContent;\n        } else {\n          return node.innerText;\n        }\n      }\n    }\n  }\n\n  isVisible(node) {\n    let mapName, style;\n    // if node is area, check visibility of relevant image\n    if (node.tagName === \"AREA\") {\n      mapName = document.evaluate(\"./ancestor::map/@name\", node, null, XPathResult.STRING_TYPE, null).stringValue;\n      node = document.querySelector(`img[usemap=\"#${mapName}\"]`);\n      if (node == null) {\n        return false;\n      }\n    }\n\n    while (node) {\n      style = window.getComputedStyle(node);\n      if (style.display === \"none\" || style.visibility === \"hidden\" || parseFloat(style.opacity) === 0) {\n        return false;\n      }\n      node = node.parentElement;\n    }\n\n    return true;\n  }\n\n\n  isDisabled(node) {\n    let xpath = \"parent::optgroup[@disabled] | \\\n                 ancestor::select[@disabled] | \\\n                 parent::fieldset[@disabled] | \\\n                 ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]\";\n\n    return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;\n  }\n\n  path(node) {\n    let nodes = [node];\n    let parent = node.parentNode;\n    while (parent !== document) {\n      nodes.unshift(parent);\n      parent = parent.parentNode;\n    }\n\n    let selectors = nodes.map(node =\u003e {\n      let prevSiblings = [];\n      let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n\n      for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n        prevSiblings.push(xpath.snapshotItem(i));\n      }\n\n      return `${node.tagName}[${(prevSiblings.length + 1)}]`;\n    });\n\n    return `//${selectors.join(\"/\")}`;\n  }\n\n  set(node, value) {\n    if (node.readOnly) return;\n\n    if (node.maxLength \u003e= 0) {\n      value = value.substr(0, node.maxLength);\n    }\n\n    this.trigger(node, \"focus\");\n    this.setValue(node, \"\");\n\n    if (node.type == \"number\" || node.type == \"date\") {\n      this.setValue(node, value);\n    } else if (node.type == \"time\") {\n      this.setValue(node, new Date(value).toTimeString().split(\" \")[0]);\n    } else if (node.type == \"datetime-local\") {\n      value = new Date(value);\n      let year = value.getFullYear();\n      let month = (\"0\" + (value.getMonth() + 1)).slice(-2);\n      let date = (\"0\" + value.getDate()).slice(-2);\n      let hour = (\"0\" + value.getHours()).slice(-2);\n      let min = (\"0\" + value.getMinutes()).slice(-2);\n      let sec = (\"0\" + value.getSeconds()).slice(-2);\n      this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);\n    } else {\n      for (let i = 0; i \u003c value.length; i++) {\n        let char = value[i];\n        let keyCode = this.characterToKeyCode(char);\n        this.keyupdowned(node, \"keydown\", keyCode);\n        this.setValue(node, node.value + char);\n\n        this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));\n        this.keyupdowned(node, \"keyup\", keyCode);\n      }\n    }\n\n    this.changed(node);\n    this.input(node);\n    this.trigger(node, \"blur\");\n  }\n\n  setValue(node, value) {\n    let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set;\n    let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, \"value\").set;\n\n    if (node.tagName.toLowerCase() === 'input') {\n      return nativeInputValueSetter.call(node, value);\n    }\n    return nativeTextareaValueSetter.call(node, value);\n  }\n\n  input(node) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"input\", true, false);\n    node.dispatchEvent(event);\n  }\n\n  keyupdowned(node, eventName, keyCode) {\n    let event = document.createEvent(\"UIEvents\");\n    event.initEvent(eventName, true, true);\n    event.keyCode  = keyCode;\n    event.charCode = 0;\n    node.dispatchEvent(event);\n  }\n\n  keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {\n    event = document.createEvent(\"UIEvents\");\n    event.initEvent(\"keypress\", true, true);\n    event.window   = window;\n    event.altKey   = altKey;\n    event.ctrlKey  = ctrlKey;\n    event.shiftKey = shiftKey;\n    event.metaKey  = metaKey;\n    event.keyCode  = keyCode;\n    event.charCode = charCode;\n    node.dispatchEvent(event);\n  }\n\n  characterToKeyCode(char) {\n    const specialKeys = {\n      96: 192,  // `\n      45: 189,  // -\n      61: 187,  // =\n      91: 219,  // [\n      93: 221,  // ]\n      92: 220,  // \\\n      59: 186,  // ;\n      39: 222,  // '\n      44: 188,  // ,\n      46: 190,  // .\n      47: 191,  // /\n      127: 46,  // delete\n      126: 192, // ~\n      33: 49,   // !\n      64: 50,   // @\n      35: 51,   // #\n      36: 52,   // $\n      37: 53,   // %\n      94: 54,   // ^\n      38: 55,   // \u0026\n      42: 56,   // *\n      40: 57,   // (\n      41: 48,   // )\n      95: 189,  // _\n      43: 187,  // +\n      123: 219, // {\n      125: 221, // }\n      124: 220, // |\n      58: 186,  // :\n      34: 222,  // \"\n      60: 188,  // \u003c\n      62: 190,  // \u003e\n      63: 191,  // ?\n    }\n\n    let code = char.toUpperCase().charCodeAt(0);\n    return specialKeys[code] || code;\n  }\n\n  scrollIntoViewport(node) {\n    let areaImage = this._getAreaImage(node);\n\n    if (areaImage) {\n      return this.scrollIntoViewport(areaImage);\n    } else {\n      node.scrollIntoViewIfNeeded();\n\n      if (!this._isInViewport(node)) {\n        node.scrollIntoView({block: \"center\", inline: \"center\", behavior: \"instant\"});\n        return this._isInViewport(node);\n      }\n\n      return true;\n    }\n  }\n\n  mouseEventTest(node, name, x, y) {\n    let frameOffset = this._frameOffset();\n    x -= frameOffset.left;\n    y -= frameOffset.top;\n\n    let element = document.elementFromPoint(x, y);\n\n    let el = element;\n    while (el) {\n      if (el == node) {\n        return true;\n      } else {\n        el = el.parentNode;\n      }\n    }\n\n    let selector = element \u0026\u0026 this._getSelector(element) || \"none\";\n    throw new MouseEventFailed([name, selector, x, y].join(\", \"));\n  }\n\n  _getAreaImage(node) {\n    if (\"area\" == node.tagName.toLowerCase()) {\n      let map = node.parentNode;\n      if (map.tagName.toLowerCase() != \"map\") {\n        throw new Error(\"the area is not within a map\");\n      }\n\n      let mapName = map.getAttribute(\"name\");\n      if (typeof mapName === \"undefined\" || mapName === null) {\n        throw new Error(\"area's parent map must have a name\");\n      }\n\n      mapName = `#${mapName.toLowerCase()}`;\n      let imageNode = this.find(\"css\", `img[usemap='${mapName}']`)[0];\n      if (typeof imageNode === \"undefined\" || imageNode === null) {\n        throw new Error(\"no image matches the map\");\n      }\n\n      return imageNode;\n    }\n  }\n\n  _frameOffset() {\n    let win = window;\n    let offset = { top: 0, left: 0 };\n\n    while (win.frameElement) {\n      let rect = win.frameElement.getClientRects()[0];\n      let style = win.getComputedStyle(win.frameElement);\n      win = win.parent;\n\n      offset.top += rect.top + parseInt(style.getPropertyValue(\"padding-top\"), 10)\n      offset.left += rect.left + parseInt(style.getPropertyValue(\"padding-left\"), 10)\n    }\n\n    return offset;\n  }\n\n  _getSelector(el) {\n    let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + \" \" : \"\";\n    selector += el.tagName.toLowerCase();\n    if (el.id) { selector += `#${el.id}` };\n    el.classList.forEach(c =\u003e selector += `.${c}`);\n    return selector;\n  }\n\n  _isInViewport(node) {\n    let rect = node.getBoundingClientRect();\n    return rect.top \u003e= 0 \u0026\u0026\n           rect.left \u003e= 0 \u0026\u0026\n           rect.bottom \u003c= window.innerHeight \u0026\u0026\n           rect.right \u003c= window.innerWidth;\n  }\n\n  select(node, value) {\n    if (this.isDisabled(node)) {\n      return false;\n    } else if (value == false \u0026\u0026 !node.parentNode.multiple) {\n      return false;\n    } else {\n      this.trigger(node.parentNode, \"focus\");\n\n      node.selected = value;\n      this.changed(node);\n\n      this.trigger(node.parentNode, \"blur\");\n      return true;\n    }\n  }\n\n  changed(node) {\n    let element;\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"change\", true, false);\n\n    // In the case of an OPTION tag, the change event should come\n    // from the parent SELECT\n    if (node.nodeName == \"OPTION\") {\n      element = node.parentNode\n      if (element.nodeName == \"OPTGROUP\") {\n        element = element.parentNode\n      }\n      element\n    } else {\n      element = node\n    }\n\n    element.dispatchEvent(event)\n  }\n\n  trigger(node, name, options = {}) {\n    let event;\n\n    if (EVENTS.MOUSE.indexOf(name) != -1) {\n      event = document.createEvent(\"MouseEvent\");\n      event.initMouseEvent(\n        name, true, true, window, 0,\n        options[\"screenX\"] || 0, options[\"screenY\"] || 0,\n        options[\"clientX\"] || 0, options[\"clientY\"] || 0,\n        options[\"ctrlKey\"] || false,\n        options[\"altKey\"] || false,\n        options[\"shiftKey\"] || false,\n        options[\"metaKey\"] || false,\n        options[\"button\"] || 0, null\n      )\n    } else if (EVENTS.FOCUS.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else if (EVENTS.FORM.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else {\n      throw \"Unknown event\";\n    }\n\n    node.dispatchEvent(event);\n  }\n\n  obtainEvent(name) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(name, true, true);\n    return event;\n  }\n\n  getAttributes(node) {\n    let attrs = {};\n    for (let i = 0, len = node.attributes.length; i \u003c len; i++) {\n      let attr = node.attributes[i];\n      attrs[attr.name] = attr.value.replace(\"\\n\", \"\\\\n\");\n    }\n\n    return this._json.stringify(attrs);\n  }\n\n  getAttribute(node, name) {\n    if (name == \"checked\" || name == \"selected\") {\n      return node[name];\n    } else {\n      return node.getAttribute(name);\n    }\n  }\n\n  value(node) {\n    if (node.tagName == \"SELECT\" \u0026\u0026 node.multiple) {\n      let result = []\n\n      for (let i = 0, len = node.children.length; i \u003c len; i++) {\n        let option = node.children[i];\n        if (option.selected) {\n          result.push(option.value);\n        }\n      }\n\n      return result;\n    } else {\n      return node.value;\n    }\n  }\n\n  deleteText(node) {\n    let range = document.createRange();\n    range.selectNodeContents(node);\n    window.getSelection().removeAllRanges();\n    window.getSelection().addRange(range);\n    window.getSelection().deleteFromDocument();\n  }\n\n  containsSelection(node) {\n    let selectedNode = document.getSelection().focusNode;\n\n    if (!selectedNode) {\n      return false;\n    }\n\n    if (selectedNode.nodeType == 3) {\n      selectedNode = selectedNode.parentNode;\n    }\n\n    return node.contains(selectedNode);\n  }\n\n  isCyclic(object) {\n    if (Array.isArray(object) \u0026\u0026 object.every(n =\u003e n instanceof Node)) {\n      return false;\n    }\n\n    try {\n      this._json.stringify(object);\n      return false;\n    } catch (e) {\n      return true;\n    }\n  }\n\n  // This command is purely for testing error handling\n  browserError() {\n    throw new Error(\"zomg\");\n  }\n}\n\nwindow._cuprite = new Cuprite;\n","contextId":1,"returnByValue":true},"id":10}
    <<< {"id":8,"result":{"identifier":"1"}}
    <<< {"id":9,"result":{"identifier":"2"}}

>>> {"method":"Runtime.evaluate","params":{"expression":"/* global $ */\ndocument.addEventListener('DOMContentLoaded', function() {\n  // kill bootstrap transitions\n  $.support.transition = false\n\n  // kill all CSS transitions\n})\n","contextId":1,"returnByValue":true},"id":11}
    <<< {"id":10,"result":{"result":{"type":"object","value":{"_json":{}}}}}
    <<< {"id":5,"result":{"windowId":2,"bounds":{"left":0,"top":0,"width":1200,"height":900,"windowState":"normal"}}}

>>> {"method":"Browser.getWindowForTarget","params":{"targetId":"F8F11DFDF7A67282EBC76EB6DAB06B46"},"id":5}
    <<< {"id":11,"result":{"result":{"type":"undefined"}}}

>>> {"method":"Browser.setWindowBounds","params":{"windowId":2,"bounds":{"windowState":"normal"}},"id":6}
    <<< {"id":6,"result":{}}

>>> {"method":"Browser.setWindowBounds","params":{"windowId":2,"bounds":{"width":1200,"height":900,"windowState":"normal"}},"id":7}
    <<< {"id":7,"result":{}}

>>> {"method":"Emulation.setDeviceMetricsOverride","params":{"width":1200,"height":900,"deviceScaleFactor":1,"mobile":false},"id":12}
    <<< {"method":"CSS.mediaQueryResultChanged","params":{}}
    <<< {"id":12,"result":{}}

>>> {"method":"Network.setRequestInterception","params":{"patterns":[{"urlPattern":"*"}]},"id":13}

>>> {"method":"Page.getNavigationHistory","params":{},"id":14}
    <<< {"id":13,"result":{}}
    <<< {"id":14,"result":{"currentIndex":0,"entries":[{"id":2,"url":"about:blank","userTypedURL":"about:blank","title":"","transitionType":"typed"}]}}

>>> {"method":"Network.setRequestInterception","params":{"patterns":[{"urlPattern":"*"}]},"id":15}
    <<< {"id":15,"result":{}}

>>> {"method":"Network.setCookie","params":{"name":"tracking","value":"%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D","domain":"127.0.0.1"},"id":16}
    <<< {"id":16,"result":{"success":true}}

>>> {"method":"Page.navigate","params":{"url":"http://127.0.0.1:43279/companyslug-2"},"id":17}
    <<< {"method":"Network.requestWillBeSent","params":{"requestId":"A61D1200A0A4EE2FE6909C46DD8C4A79","loaderId":"A61D1200A0A4EE2FE6909C46DD8C4A79","documentURL":"http://127.0.0.1:43279/companyslug-2","request":{"url":"http://127.0.0.1:43279/companyslug-2","method":"GET","headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"no-referrer-when-downgrade"},"timestamp":4237859.195411,"wallTime":1552573170.380735,"initiator":{"type":"other"},"type":"Document","frameId":"F8F11DFDF7A67282EBC76EB6DAB06B46","hasUserGesture":false}}
    <<< {"method":"Network.requestIntercepted","params":{"interceptionId":"interception-job-1.0","request":{"url":"http://127.0.0.1:43279/companyslug-2","method":"GET","headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","Cookie":"tracking=%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D"},"initialPriority":"VeryHigh","referrerPolicy":"no-referrer-when-downgrade"},"frameId":"F8F11DFDF7A67282EBC76EB6DAB06B46","resourceType":"Document","isNavigationRequest":true}}

>>> {"method":"Network.continueInterceptedRequest","params":{"interceptionId":"interception-job-1.0"},"id":18}
    <<< {"id":18,"result":{}}
    <<< {"method":"Network.requestWillBeSent","params":{"requestId":"A61D1200A0A4EE2FE6909C46DD8C4A79","loaderId":"A61D1200A0A4EE2FE6909C46DD8C4A79","documentURL":"http://127.0.0.1:43279/wayne-enterprises-2","request":{"url":"http://127.0.0.1:43279/wayne-enterprises-2","method":"GET","headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"no-referrer-when-downgrade"},"timestamp":4237859.365825,"wallTime":1552573170.551149,"initiator":{"type":"other"},"redirectResponse":{"url":"http://127.0.0.1:43279/companyslug-2","status":301,"statusText":"Moved Permanently","headers":{"X-Frame-Options":"SAMEORIGIN","X-Xss-Protection":"1; mode=block","X-Content-Type-Options":"nosniff","Location":"http://127.0.0.1:43279/wayne-enterprises-2","Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache","X-Request-Id":"f6eb29b9-8d12-41e7-848d-9d55600da5a1","X-Runtime":"0.157283","Server":"WEBrick/1.4.2 (Ruby/2.5.3/2018-10-18)","Date":"Thu, 14 Mar 2019 14:19:30 GMT","Content-Length":"117","Connection":"Keep-Alive","Set-Cookie":"locale=de; path=/; expires=Sun, 14 Apr 2019 13:19:30 -0000"},"headersText":"HTTP/1.1 301 Moved Permanently\r\nX-Frame-Options: SAMEORIGIN\r\nX-Xss-Protection: 1; mode=block\r\nX-Content-Type-Options: nosniff\r\nLocation: http://127.0.0.1:43279/wayne-enterprises-2\r\nContent-Type: text/html; charset=utf-8\r\nCache-Control: no-cache\r\nX-Request-Id: f6eb29b9-8d12-41e7-848d-9d55600da5a1\r\nX-Runtime: 0.157283\r\nServer: WEBrick/1.4.2 (Ruby/2.5.3/2018-10-18)\r\nDate: Thu, 14 Mar 2019 14:19:30 GMT\r\nContent-Length: 117\r\nConnection: Keep-Alive\r\nSet-Cookie: locale=de; path=/; expires=Sun, 14 Apr 2019 13:19:30 -0000\r\n\r\n","mimeType":"text/html","requestHeaders":{"Host":"127.0.0.1:43279","Connection":"keep-alive","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","Accept-Encoding":"gzip, deflate, br","Cookie":"tracking=%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D"},"requestHeadersText":"GET /companyslug-2 HTTP/1.1\r\nHost: 127.0.0.1:43279\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nCookie: tracking=%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D\r\n","connectionReused":false,"connectionId":12,"remoteIPAddress":"127.0.0.1","remotePort":43279,"fromDiskCache":false,"fromServiceWorker":false,"encodedDataLength":536,"timing":{"requestTime":4237859.19903,"proxyStart":-1,"proxyEnd":-1,"dnsStart":0.351,"dnsEnd":0.389,"connectStart":0.389,"connectEnd":0.575,"sslStart":-1,"sslEnd":-1,"workerStart":-1,"workerReady":-1,"sendStart":0.624,"sendEnd":0.656,"pushStart":0,"pushEnd":0,"receiveHeadersEnd":165.671},"protocol":"http/1.1","securityState":"neutral"},"type":"Document","frameId":"F8F11DFDF7A67282EBC76EB6DAB06B46","hasUserGesture":false}}
    <<< {"method":"Network.requestIntercepted","params":{"interceptionId":"interception-job-1.1","request":{"url":"http://127.0.0.1:43279/wayne-enterprises-2","method":"GET","headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","Cookie":"tracking=%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D; locale=de"},"initialPriority":"VeryHigh","referrerPolicy":"no-referrer-when-downgrade"},"frameId":"F8F11DFDF7A67282EBC76EB6DAB06B46","resourceType":"Document","isNavigationRequest":true}}

>>> {"method":"Network.continueInterceptedRequest","params":{"interceptionId":"interception-job-1.1"},"id":19}
    <<< {"id":19,"result":{}}

>>> {"method":"Runtime.callFunctionOn","params":{"functionDeclaration":"function() { return window.location.href }","arguments":[],"executionContextId":1},"id":20}

>>> {"method":"Target.detachFromTarget","params":{"sessionId":"A63DAC7C5D613751CB0AB0B3EF9D7C59"},"id":8}
F

Failures:

     1.1) Failure/Error: page.visit "/companyslug-#{company.id}"

          Capybara::Cuprite::DeadBrowser:
            Chrome is dead
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:41:in `wait'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:196:in `command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:77:in `visit'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:31:in `visit'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/session.rb:274:in `visit'
          # ./spec/features/company/vcard_spec.rb:35:in `block (2 levels) in <top (required)>'

     1.2) Failure/Error: raise DeadBrowser unless message

          Capybara::Cuprite::DeadBrowser:
            Chrome is dead
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:41:in `wait'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:196:in `command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/runtime.rb:81:in `call'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/runtime.rb:32:in `evaluate'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/frame.rb:19:in `frame_url'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:43:in `frame_url'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:36:in `current_url'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/session.rb:217:in `current_url'
          # /bundle/2.5.3/gems/capybara-screenshot-1.0.14/lib/capybara-screenshot/rspec.rb:55:in `block in after_failed_example'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:351:in `using_session'
          # /bundle/2.5.3/gems/capybara-screenshot-1.0.14/lib/capybara-screenshot/rspec.rb:54:in `after_failed_example'
          # /bundle/2.5.3/gems/capybara-screenshot-1.0.14/lib/capybara-screenshot/rspec.rb:84:in `block (2 levels) in <top (required)>'

     1.3) Failure/Error: @sock.write(data)

          Errno::EPIPE:
            Broken pipe
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/web_socket.rb:61:in `write'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/web_socket.rb:61:in `write'
          # /bundle/2.5.3/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi.rb:228:in `send_frame'
          # /bundle/2.5.3/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi.rb:191:in `frame'
          # /bundle/2.5.3/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi.rb:154:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/web_socket.rb:65:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:56:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:214:in `quit'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:209:in `restart'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:236:in `rescue in command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:232:in `command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:89:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/targets.rb:80:in `reset'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:205:in `reset'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:131:in `reset!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/session.rb:127:in `reset!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:314:in `block in reset_sessions!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:314:in `reverse_each'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:314:in `reset_sessions!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/rspec.rb:22:in `block (2 levels) in <top (required)>'
          # ------------------
          # --- Caused by: ---
          # Capybara::Cuprite::DeadBrowser:
          #   Chrome is dead
          #   /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:41:in `wait'

Finished in 6.98 seconds (files took 9.03 seconds to load)
1 example, 1 failure

Failed examples:
route commented 5 years ago

Yea looks fishy, I see you are using black or whitelisting, try to turn it off for a test, any better? If no again logs would be awesome

vermaxik commented 5 years ago

Yes, use blacklists, but turning off not helping:


    <<< {"id":1,"result":{"targetInfos":[{"targetId":"F477F57E939E7C973211ADB2145D6A71","type":"page","title":"","url":"about:blank","attached":false,"browserContextId":"1294B76B78ADEF3F3F9F9C63E22789FA"}]}}

>>> {"method":"Target.createBrowserContext","params":{},"id":2}
    <<< {"id":2,"result":{"browserContextId":"A29200EC561BEDFD2D45DDC2FA8131C9"}}

>>> {"method":"Target.createTarget","params":{"url":"about:blank","browserContextId":"A29200EC561BEDFD2D45DDC2FA8131C9"},"id":3}
    <<< {"id":3,"result":{"targetId":"4DB9991144C865BC1A76C00D74208FF9"}}

>>> {"method":"Target.attachToTarget","params":{"targetId":"4DB9991144C865BC1A76C00D74208FF9"},"id":4}
    <<< {"method":"Target.attachedToTarget","params":{"sessionId":"10A96BAE9B51A6B48F861DE89CB669A2","targetInfo":{"targetId":"4DB9991144C865BC1A76C00D74208FF9","type":"page","title":"","url":"about:blank","attached":true,"browserContextId":"A29200EC561BEDFD2D45DDC2FA8131C9"},"waitingForDebugger":false}}
    <<< {"id":4,"result":{"sessionId":"10A96BAE9B51A6B48F861DE89CB669A2"}}

>>> {"method":"Page.enable","params":{},"id":1}
    <<< {"id":1,"result":{}}

>>> {"method":"DOM.enable","params":{},"id":2}
    <<< {"id":2,"result":{}}

>>> {"method":"CSS.enable","params":{},"id":3}
    <<< {"id":3,"result":{}}

>>> {"method":"Runtime.enable","params":{},"id":4}
    <<< {"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"origin":"://","name":"","auxData":{"isDefault":true,"type":"default","frameId":"4DB9991144C865BC1A76C00D74208FF9"}}}}
    <<< {"id":4,"result":{}}

>>> {"method":"Log.enable","params":{},"id":5}
    <<< {"id":5,"result":{}}

>>> {"method":"Network.enable","params":{},"id":6}
    <<< {"id":6,"result":{}}

>>> {"method":"Page.setDownloadBehavior","params":{"behavior":"allow","downloadPath":"/app/capybara"},"id":7}
    <<< {"id":7,"result":{}}

>>> {"method":"Page.addScriptToEvaluateOnNewDocument","params":{"source":"class InvalidSelector extends Error {}\nclass TimedOutPromise extends Error {}\nclass MouseEventFailed extends Error {}\n\nconst EVENTS = {\n  FOCUS: [\"blur\", \"focus\", \"focusin\", \"focusout\"],\n  MOUSE: [\"click\", \"dblclick\", \"mousedown\", \"mouseenter\", \"mouseleave\",\n          \"mousemove\", \"mouseover\", \"mouseout\", \"mouseup\", \"contextmenu\"],\n  FORM: [\"submit\"]\n}\n\nclass Cuprite {\n  constructor() {\n    this._json = JSON; // In case someone overrides it like mootools\n  }\n\n  find(method, selector, within = document) {\n    try {\n      let results = [];\n\n      if (method == \"xpath\") {\n        let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n        for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n          results.push(xpath.snapshotItem(i));\n        }\n      } else {\n        results = Array.from(within.querySelectorAll(selector));\n      }\n\n      return results;\n    } catch (error) {\n      // DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code\n      if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {\n        throw new InvalidSelector;\n      } else {\n        throw error;\n      }\n    }\n  }\n\n  parents(node) {\n    let nodes = [];\n    let parent = node.parentNode;\n    while (parent != document) {\n      nodes.push(parent);\n      parent = parent.parentNode;\n    }\n    return nodes;\n  }\n\n  visibleText(node) {\n    if (this.isVisible(node)) {\n      if (node.nodeName == \"TEXTAREA\") {\n        return node.textContent;\n      } else {\n        if (node instanceof SVGElement) {\n          return node.textContent;\n        } else {\n          return node.innerText;\n        }\n      }\n    }\n  }\n\n  isVisible(node) {\n    let mapName, style;\n    // if node is area, check visibility of relevant image\n    if (node.tagName === \"AREA\") {\n      mapName = document.evaluate(\"./ancestor::map/@name\", node, null, XPathResult.STRING_TYPE, null).stringValue;\n      node = document.querySelector(`img[usemap=\"#${mapName}\"]`);\n      if (node == null) {\n        return false;\n      }\n    }\n\n    while (node) {\n      style = window.getComputedStyle(node);\n      if (style.display === \"none\" || style.visibility === \"hidden\" || parseFloat(style.opacity) === 0) {\n        return false;\n      }\n      node = node.parentElement;\n    }\n\n    return true;\n  }\n\n\n  isDisabled(node) {\n    let xpath = \"parent::optgroup[@disabled] | \\\n                 ancestor::select[@disabled] | \\\n                 parent::fieldset[@disabled] | \\\n                 ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]\";\n\n    return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;\n  }\n\n  path(node) {\n    let nodes = [node];\n    let parent = node.parentNode;\n    while (parent !== document) {\n      nodes.unshift(parent);\n      parent = parent.parentNode;\n    }\n\n    let selectors = nodes.map(node =\u003e {\n      let prevSiblings = [];\n      let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n\n      for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n        prevSiblings.push(xpath.snapshotItem(i));\n      }\n\n      return `${node.tagName}[${(prevSiblings.length + 1)}]`;\n    });\n\n    return `//${selectors.join(\"/\")}`;\n  }\n\n  set(node, value) {\n    if (node.readOnly) return;\n\n    if (node.maxLength \u003e= 0) {\n      value = value.substr(0, node.maxLength);\n    }\n\n    this.trigger(node, \"focus\");\n    this.setValue(node, \"\");\n\n    if (node.type == \"number\" || node.type == \"date\") {\n      this.setValue(node, value);\n    } else if (node.type == \"time\") {\n      this.setValue(node, new Date(value).toTimeString().split(\" \")[0]);\n    } else if (node.type == \"datetime-local\") {\n      value = new Date(value);\n      let year = value.getFullYear();\n      let month = (\"0\" + (value.getMonth() + 1)).slice(-2);\n      let date = (\"0\" + value.getDate()).slice(-2);\n      let hour = (\"0\" + value.getHours()).slice(-2);\n      let min = (\"0\" + value.getMinutes()).slice(-2);\n      let sec = (\"0\" + value.getSeconds()).slice(-2);\n      this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);\n    } else {\n      for (let i = 0; i \u003c value.length; i++) {\n        let char = value[i];\n        let keyCode = this.characterToKeyCode(char);\n        this.keyupdowned(node, \"keydown\", keyCode);\n        this.setValue(node, node.value + char);\n\n        this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));\n        this.keyupdowned(node, \"keyup\", keyCode);\n      }\n    }\n\n    this.changed(node);\n    this.input(node);\n    this.trigger(node, \"blur\");\n  }\n\n  setValue(node, value) {\n    let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set;\n    let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, \"value\").set;\n\n    if (node.tagName.toLowerCase() === 'input') {\n      return nativeInputValueSetter.call(node, value);\n    }\n    return nativeTextareaValueSetter.call(node, value);\n  }\n\n  input(node) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"input\", true, false);\n    node.dispatchEvent(event);\n  }\n\n  keyupdowned(node, eventName, keyCode) {\n    let event = document.createEvent(\"UIEvents\");\n    event.initEvent(eventName, true, true);\n    event.keyCode  = keyCode;\n    event.charCode = 0;\n    node.dispatchEvent(event);\n  }\n\n  keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {\n    event = document.createEvent(\"UIEvents\");\n    event.initEvent(\"keypress\", true, true);\n    event.window   = window;\n    event.altKey   = altKey;\n    event.ctrlKey  = ctrlKey;\n    event.shiftKey = shiftKey;\n    event.metaKey  = metaKey;\n    event.keyCode  = keyCode;\n    event.charCode = charCode;\n    node.dispatchEvent(event);\n  }\n\n  characterToKeyCode(char) {\n    const specialKeys = {\n      96: 192,  // `\n      45: 189,  // -\n      61: 187,  // =\n      91: 219,  // [\n      93: 221,  // ]\n      92: 220,  // \\\n      59: 186,  // ;\n      39: 222,  // '\n      44: 188,  // ,\n      46: 190,  // .\n      47: 191,  // /\n      127: 46,  // delete\n      126: 192, // ~\n      33: 49,   // !\n      64: 50,   // @\n      35: 51,   // #\n      36: 52,   // $\n      37: 53,   // %\n      94: 54,   // ^\n      38: 55,   // \u0026\n      42: 56,   // *\n      40: 57,   // (\n      41: 48,   // )\n      95: 189,  // _\n      43: 187,  // +\n      123: 219, // {\n      125: 221, // }\n      124: 220, // |\n      58: 186,  // :\n      34: 222,  // \"\n      60: 188,  // \u003c\n      62: 190,  // \u003e\n      63: 191,  // ?\n    }\n\n    let code = char.toUpperCase().charCodeAt(0);\n    return specialKeys[code] || code;\n  }\n\n  scrollIntoViewport(node) {\n    let areaImage = this._getAreaImage(node);\n\n    if (areaImage) {\n      return this.scrollIntoViewport(areaImage);\n    } else {\n      node.scrollIntoViewIfNeeded();\n\n      if (!this._isInViewport(node)) {\n        node.scrollIntoView({block: \"center\", inline: \"center\", behavior: \"instant\"});\n        return this._isInViewport(node);\n      }\n\n      return true;\n    }\n  }\n\n  mouseEventTest(node, name, x, y) {\n    let frameOffset = this._frameOffset();\n    x -= frameOffset.left;\n    y -= frameOffset.top;\n\n    let element = document.elementFromPoint(x, y);\n\n    let el = element;\n    while (el) {\n      if (el == node) {\n        return true;\n      } else {\n        el = el.parentNode;\n      }\n    }\n\n    let selector = element \u0026\u0026 this._getSelector(element) || \"none\";\n    throw new MouseEventFailed([name, selector, x, y].join(\", \"));\n  }\n\n  _getAreaImage(node) {\n    if (\"area\" == node.tagName.toLowerCase()) {\n      let map = node.parentNode;\n      if (map.tagName.toLowerCase() != \"map\") {\n        throw new Error(\"the area is not within a map\");\n      }\n\n      let mapName = map.getAttribute(\"name\");\n      if (typeof mapName === \"undefined\" || mapName === null) {\n        throw new Error(\"area's parent map must have a name\");\n      }\n\n      mapName = `#${mapName.toLowerCase()}`;\n      let imageNode = this.find(\"css\", `img[usemap='${mapName}']`)[0];\n      if (typeof imageNode === \"undefined\" || imageNode === null) {\n        throw new Error(\"no image matches the map\");\n      }\n\n      return imageNode;\n    }\n  }\n\n  _frameOffset() {\n    let win = window;\n    let offset = { top: 0, left: 0 };\n\n    while (win.frameElement) {\n      let rect = win.frameElement.getClientRects()[0];\n      let style = win.getComputedStyle(win.frameElement);\n      win = win.parent;\n\n      offset.top += rect.top + parseInt(style.getPropertyValue(\"padding-top\"), 10)\n      offset.left += rect.left + parseInt(style.getPropertyValue(\"padding-left\"), 10)\n    }\n\n    return offset;\n  }\n\n  _getSelector(el) {\n    let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + \" \" : \"\";\n    selector += el.tagName.toLowerCase();\n    if (el.id) { selector += `#${el.id}` };\n    el.classList.forEach(c =\u003e selector += `.${c}`);\n    return selector;\n  }\n\n  _isInViewport(node) {\n    let rect = node.getBoundingClientRect();\n    return rect.top \u003e= 0 \u0026\u0026\n           rect.left \u003e= 0 \u0026\u0026\n           rect.bottom \u003c= window.innerHeight \u0026\u0026\n           rect.right \u003c= window.innerWidth;\n  }\n\n  select(node, value) {\n    if (this.isDisabled(node)) {\n      return false;\n    } else if (value == false \u0026\u0026 !node.parentNode.multiple) {\n      return false;\n    } else {\n      this.trigger(node.parentNode, \"focus\");\n\n      node.selected = value;\n      this.changed(node);\n\n      this.trigger(node.parentNode, \"blur\");\n      return true;\n    }\n  }\n\n  changed(node) {\n    let element;\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"change\", true, false);\n\n    // In the case of an OPTION tag, the change event should come\n    // from the parent SELECT\n    if (node.nodeName == \"OPTION\") {\n      element = node.parentNode\n      if (element.nodeName == \"OPTGROUP\") {\n        element = element.parentNode\n      }\n      element\n    } else {\n      element = node\n    }\n\n    element.dispatchEvent(event)\n  }\n\n  trigger(node, name, options = {}) {\n    let event;\n\n    if (EVENTS.MOUSE.indexOf(name) != -1) {\n      event = document.createEvent(\"MouseEvent\");\n      event.initMouseEvent(\n        name, true, true, window, 0,\n        options[\"screenX\"] || 0, options[\"screenY\"] || 0,\n        options[\"clientX\"] || 0, options[\"clientY\"] || 0,\n        options[\"ctrlKey\"] || false,\n        options[\"altKey\"] || false,\n        options[\"shiftKey\"] || false,\n        options[\"metaKey\"] || false,\n        options[\"button\"] || 0, null\n      )\n    } else if (EVENTS.FOCUS.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else if (EVENTS.FORM.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else {\n      throw \"Unknown event\";\n    }\n\n    node.dispatchEvent(event);\n  }\n\n  obtainEvent(name) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(name, true, true);\n    return event;\n  }\n\n  getAttributes(node) {\n    let attrs = {};\n    for (let i = 0, len = node.attributes.length; i \u003c len; i++) {\n      let attr = node.attributes[i];\n      attrs[attr.name] = attr.value.replace(\"\\n\", \"\\\\n\");\n    }\n\n    return this._json.stringify(attrs);\n  }\n\n  getAttribute(node, name) {\n    if (name == \"checked\" || name == \"selected\") {\n      return node[name];\n    } else {\n      return node.getAttribute(name);\n    }\n  }\n\n  value(node) {\n    if (node.tagName == \"SELECT\" \u0026\u0026 node.multiple) {\n      let result = []\n\n      for (let i = 0, len = node.children.length; i \u003c len; i++) {\n        let option = node.children[i];\n        if (option.selected) {\n          result.push(option.value);\n        }\n      }\n\n      return result;\n    } else {\n      return node.value;\n    }\n  }\n\n  deleteText(node) {\n    let range = document.createRange();\n    range.selectNodeContents(node);\n    window.getSelection().removeAllRanges();\n    window.getSelection().addRange(range);\n    window.getSelection().deleteFromDocument();\n  }\n\n  containsSelection(node) {\n    let selectedNode = document.getSelection().focusNode;\n\n    if (!selectedNode) {\n      return false;\n    }\n\n    if (selectedNode.nodeType == 3) {\n      selectedNode = selectedNode.parentNode;\n    }\n\n    return node.contains(selectedNode);\n  }\n\n  isCyclic(object) {\n    if (Array.isArray(object) \u0026\u0026 object.every(n =\u003e n instanceof Node)) {\n      return false;\n    }\n\n    try {\n      this._json.stringify(object);\n      return false;\n    } catch (e) {\n      return true;\n    }\n  }\n\n  // This command is purely for testing error handling\n  browserError() {\n    throw new Error(\"zomg\");\n  }\n}\n\nwindow._cuprite = new Cuprite;\n"},"id":8}

>>> {"method":"Page.addScriptToEvaluateOnNewDocument","params":{"source":"/* global $ */\ndocument.addEventListener('DOMContentLoaded', function() {\n  // kill bootstrap transitions\n  $.support.transition = false\n\n  // kill all CSS transitions\n})\n"},"id":9}

>>> {"method":"Runtime.evaluate","params":{"expression":"class InvalidSelector extends Error {}\nclass TimedOutPromise extends Error {}\nclass MouseEventFailed extends Error {}\n\nconst EVENTS = {\n  FOCUS: [\"blur\", \"focus\", \"focusin\", \"focusout\"],\n  MOUSE: [\"click\", \"dblclick\", \"mousedown\", \"mouseenter\", \"mouseleave\",\n          \"mousemove\", \"mouseover\", \"mouseout\", \"mouseup\", \"contextmenu\"],\n  FORM: [\"submit\"]\n}\n\nclass Cuprite {\n  constructor() {\n    this._json = JSON; // In case someone overrides it like mootools\n  }\n\n  find(method, selector, within = document) {\n    try {\n      let results = [];\n\n      if (method == \"xpath\") {\n        let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n        for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n          results.push(xpath.snapshotItem(i));\n        }\n      } else {\n        results = Array.from(within.querySelectorAll(selector));\n      }\n\n      return results;\n    } catch (error) {\n      // DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code\n      if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {\n        throw new InvalidSelector;\n      } else {\n        throw error;\n      }\n    }\n  }\n\n  parents(node) {\n    let nodes = [];\n    let parent = node.parentNode;\n    while (parent != document) {\n      nodes.push(parent);\n      parent = parent.parentNode;\n    }\n    return nodes;\n  }\n\n  visibleText(node) {\n    if (this.isVisible(node)) {\n      if (node.nodeName == \"TEXTAREA\") {\n        return node.textContent;\n      } else {\n        if (node instanceof SVGElement) {\n          return node.textContent;\n        } else {\n          return node.innerText;\n        }\n      }\n    }\n  }\n\n  isVisible(node) {\n    let mapName, style;\n    // if node is area, check visibility of relevant image\n    if (node.tagName === \"AREA\") {\n      mapName = document.evaluate(\"./ancestor::map/@name\", node, null, XPathResult.STRING_TYPE, null).stringValue;\n      node = document.querySelector(`img[usemap=\"#${mapName}\"]`);\n      if (node == null) {\n        return false;\n      }\n    }\n\n    while (node) {\n      style = window.getComputedStyle(node);\n      if (style.display === \"none\" || style.visibility === \"hidden\" || parseFloat(style.opacity) === 0) {\n        return false;\n      }\n      node = node.parentElement;\n    }\n\n    return true;\n  }\n\n\n  isDisabled(node) {\n    let xpath = \"parent::optgroup[@disabled] | \\\n                 ancestor::select[@disabled] | \\\n                 parent::fieldset[@disabled] | \\\n                 ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]\";\n\n    return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;\n  }\n\n  path(node) {\n    let nodes = [node];\n    let parent = node.parentNode;\n    while (parent !== document) {\n      nodes.unshift(parent);\n      parent = parent.parentNode;\n    }\n\n    let selectors = nodes.map(node =\u003e {\n      let prevSiblings = [];\n      let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n\n      for (let i = 0; i \u003c xpath.snapshotLength; i++) {\n        prevSiblings.push(xpath.snapshotItem(i));\n      }\n\n      return `${node.tagName}[${(prevSiblings.length + 1)}]`;\n    });\n\n    return `//${selectors.join(\"/\")}`;\n  }\n\n  set(node, value) {\n    if (node.readOnly) return;\n\n    if (node.maxLength \u003e= 0) {\n      value = value.substr(0, node.maxLength);\n    }\n\n    this.trigger(node, \"focus\");\n    this.setValue(node, \"\");\n\n    if (node.type == \"number\" || node.type == \"date\") {\n      this.setValue(node, value);\n    } else if (node.type == \"time\") {\n      this.setValue(node, new Date(value).toTimeString().split(\" \")[0]);\n    } else if (node.type == \"datetime-local\") {\n      value = new Date(value);\n      let year = value.getFullYear();\n      let month = (\"0\" + (value.getMonth() + 1)).slice(-2);\n      let date = (\"0\" + value.getDate()).slice(-2);\n      let hour = (\"0\" + value.getHours()).slice(-2);\n      let min = (\"0\" + value.getMinutes()).slice(-2);\n      let sec = (\"0\" + value.getSeconds()).slice(-2);\n      this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);\n    } else {\n      for (let i = 0; i \u003c value.length; i++) {\n        let char = value[i];\n        let keyCode = this.characterToKeyCode(char);\n        this.keyupdowned(node, \"keydown\", keyCode);\n        this.setValue(node, node.value + char);\n\n        this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));\n        this.keyupdowned(node, \"keyup\", keyCode);\n      }\n    }\n\n    this.changed(node);\n    this.input(node);\n    this.trigger(node, \"blur\");\n  }\n\n  setValue(node, value) {\n    let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set;\n    let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, \"value\").set;\n\n    if (node.tagName.toLowerCase() === 'input') {\n      return nativeInputValueSetter.call(node, value);\n    }\n    return nativeTextareaValueSetter.call(node, value);\n  }\n\n  input(node) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"input\", true, false);\n    node.dispatchEvent(event);\n  }\n\n  keyupdowned(node, eventName, keyCode) {\n    let event = document.createEvent(\"UIEvents\");\n    event.initEvent(eventName, true, true);\n    event.keyCode  = keyCode;\n    event.charCode = 0;\n    node.dispatchEvent(event);\n  }\n\n  keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {\n    event = document.createEvent(\"UIEvents\");\n    event.initEvent(\"keypress\", true, true);\n    event.window   = window;\n    event.altKey   = altKey;\n    event.ctrlKey  = ctrlKey;\n    event.shiftKey = shiftKey;\n    event.metaKey  = metaKey;\n    event.keyCode  = keyCode;\n    event.charCode = charCode;\n    node.dispatchEvent(event);\n  }\n\n  characterToKeyCode(char) {\n    const specialKeys = {\n      96: 192,  // `\n      45: 189,  // -\n      61: 187,  // =\n      91: 219,  // [\n      93: 221,  // ]\n      92: 220,  // \\\n      59: 186,  // ;\n      39: 222,  // '\n      44: 188,  // ,\n      46: 190,  // .\n      47: 191,  // /\n      127: 46,  // delete\n      126: 192, // ~\n      33: 49,   // !\n      64: 50,   // @\n      35: 51,   // #\n      36: 52,   // $\n      37: 53,   // %\n      94: 54,   // ^\n      38: 55,   // \u0026\n      42: 56,   // *\n      40: 57,   // (\n      41: 48,   // )\n      95: 189,  // _\n      43: 187,  // +\n      123: 219, // {\n      125: 221, // }\n      124: 220, // |\n      58: 186,  // :\n      34: 222,  // \"\n      60: 188,  // \u003c\n      62: 190,  // \u003e\n      63: 191,  // ?\n    }\n\n    let code = char.toUpperCase().charCodeAt(0);\n    return specialKeys[code] || code;\n  }\n\n  scrollIntoViewport(node) {\n    let areaImage = this._getAreaImage(node);\n\n    if (areaImage) {\n      return this.scrollIntoViewport(areaImage);\n    } else {\n      node.scrollIntoViewIfNeeded();\n\n      if (!this._isInViewport(node)) {\n        node.scrollIntoView({block: \"center\", inline: \"center\", behavior: \"instant\"});\n        return this._isInViewport(node);\n      }\n\n      return true;\n    }\n  }\n\n  mouseEventTest(node, name, x, y) {\n    let frameOffset = this._frameOffset();\n    x -= frameOffset.left;\n    y -= frameOffset.top;\n\n    let element = document.elementFromPoint(x, y);\n\n    let el = element;\n    while (el) {\n      if (el == node) {\n        return true;\n      } else {\n        el = el.parentNode;\n      }\n    }\n\n    let selector = element \u0026\u0026 this._getSelector(element) || \"none\";\n    throw new MouseEventFailed([name, selector, x, y].join(\", \"));\n  }\n\n  _getAreaImage(node) {\n    if (\"area\" == node.tagName.toLowerCase()) {\n      let map = node.parentNode;\n      if (map.tagName.toLowerCase() != \"map\") {\n        throw new Error(\"the area is not within a map\");\n      }\n\n      let mapName = map.getAttribute(\"name\");\n      if (typeof mapName === \"undefined\" || mapName === null) {\n        throw new Error(\"area's parent map must have a name\");\n      }\n\n      mapName = `#${mapName.toLowerCase()}`;\n      let imageNode = this.find(\"css\", `img[usemap='${mapName}']`)[0];\n      if (typeof imageNode === \"undefined\" || imageNode === null) {\n        throw new Error(\"no image matches the map\");\n      }\n\n      return imageNode;\n    }\n  }\n\n  _frameOffset() {\n    let win = window;\n    let offset = { top: 0, left: 0 };\n\n    while (win.frameElement) {\n      let rect = win.frameElement.getClientRects()[0];\n      let style = win.getComputedStyle(win.frameElement);\n      win = win.parent;\n\n      offset.top += rect.top + parseInt(style.getPropertyValue(\"padding-top\"), 10)\n      offset.left += rect.left + parseInt(style.getPropertyValue(\"padding-left\"), 10)\n    }\n\n    return offset;\n  }\n\n  _getSelector(el) {\n    let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + \" \" : \"\";\n    selector += el.tagName.toLowerCase();\n    if (el.id) { selector += `#${el.id}` };\n    el.classList.forEach(c =\u003e selector += `.${c}`);\n    return selector;\n  }\n\n  _isInViewport(node) {\n    let rect = node.getBoundingClientRect();\n    return rect.top \u003e= 0 \u0026\u0026\n           rect.left \u003e= 0 \u0026\u0026\n           rect.bottom \u003c= window.innerHeight \u0026\u0026\n           rect.right \u003c= window.innerWidth;\n  }\n\n  select(node, value) {\n    if (this.isDisabled(node)) {\n      return false;\n    } else if (value == false \u0026\u0026 !node.parentNode.multiple) {\n      return false;\n    } else {\n      this.trigger(node.parentNode, \"focus\");\n\n      node.selected = value;\n      this.changed(node);\n\n      this.trigger(node.parentNode, \"blur\");\n      return true;\n    }\n  }\n\n  changed(node) {\n    let element;\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(\"change\", true, false);\n\n    // In the case of an OPTION tag, the change event should come\n    // from the parent SELECT\n    if (node.nodeName == \"OPTION\") {\n      element = node.parentNode\n      if (element.nodeName == \"OPTGROUP\") {\n        element = element.parentNode\n      }\n      element\n    } else {\n      element = node\n    }\n\n    element.dispatchEvent(event)\n  }\n\n  trigger(node, name, options = {}) {\n    let event;\n\n    if (EVENTS.MOUSE.indexOf(name) != -1) {\n      event = document.createEvent(\"MouseEvent\");\n      event.initMouseEvent(\n        name, true, true, window, 0,\n        options[\"screenX\"] || 0, options[\"screenY\"] || 0,\n        options[\"clientX\"] || 0, options[\"clientY\"] || 0,\n        options[\"ctrlKey\"] || false,\n        options[\"altKey\"] || false,\n        options[\"shiftKey\"] || false,\n        options[\"metaKey\"] || false,\n        options[\"button\"] || 0, null\n      )\n    } else if (EVENTS.FOCUS.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else if (EVENTS.FORM.indexOf(name) != -1) {\n      event = this.obtainEvent(name);\n    } else {\n      throw \"Unknown event\";\n    }\n\n    node.dispatchEvent(event);\n  }\n\n  obtainEvent(name) {\n    let event = document.createEvent(\"HTMLEvents\");\n    event.initEvent(name, true, true);\n    return event;\n  }\n\n  getAttributes(node) {\n    let attrs = {};\n    for (let i = 0, len = node.attributes.length; i \u003c len; i++) {\n      let attr = node.attributes[i];\n      attrs[attr.name] = attr.value.replace(\"\\n\", \"\\\\n\");\n    }\n\n    return this._json.stringify(attrs);\n  }\n\n  getAttribute(node, name) {\n    if (name == \"checked\" || name == \"selected\") {\n      return node[name];\n    } else {\n      return node.getAttribute(name);\n    }\n  }\n\n  value(node) {\n    if (node.tagName == \"SELECT\" \u0026\u0026 node.multiple) {\n      let result = []\n\n      for (let i = 0, len = node.children.length; i \u003c len; i++) {\n        let option = node.children[i];\n        if (option.selected) {\n          result.push(option.value);\n        }\n      }\n\n      return result;\n    } else {\n      return node.value;\n    }\n  }\n\n  deleteText(node) {\n    let range = document.createRange();\n    range.selectNodeContents(node);\n    window.getSelection().removeAllRanges();\n    window.getSelection().addRange(range);\n    window.getSelection().deleteFromDocument();\n  }\n\n  containsSelection(node) {\n    let selectedNode = document.getSelection().focusNode;\n\n    if (!selectedNode) {\n      return false;\n    }\n\n    if (selectedNode.nodeType == 3) {\n      selectedNode = selectedNode.parentNode;\n    }\n\n    return node.contains(selectedNode);\n  }\n\n  isCyclic(object) {\n    if (Array.isArray(object) \u0026\u0026 object.every(n =\u003e n instanceof Node)) {\n      return false;\n    }\n\n    try {\n      this._json.stringify(object);\n      return false;\n    } catch (e) {\n      return true;\n    }\n  }\n\n  // This command is purely for testing error handling\n  browserError() {\n    throw new Error(\"zomg\");\n  }\n}\n\nwindow._cuprite = new Cuprite;\n","contextId":1,"returnByValue":true},"id":10}

>>> {"method":"Runtime.evaluate","params":{"expression":"/* global $ */\ndocument.addEventListener('DOMContentLoaded', function() {\n  // kill bootstrap transitions\n  $.support.transition = false\n\n  // kill all CSS transitions\n})\n","contextId":1,"returnByValue":true},"id":11}
    <<< {"id":5,"result":{"windowId":2,"bounds":{"left":0,"top":0,"width":1200,"height":900,"windowState":"normal"}}}

>>> {"method":"Browser.getWindowForTarget","params":{"targetId":"4DB9991144C865BC1A76C00D74208FF9"},"id":5}
    <<< {"id":6,"result":{}}

>>> {"method":"Browser.setWindowBounds","params":{"windowId":2,"bounds":{"windowState":"normal"}},"id":6}
    <<< {"id":8,"result":{"identifier":"1"}}
    <<< {"id":9,"result":{"identifier":"2"}}
    <<< {"id":10,"result":{"result":{"type":"object","value":{"_json":{}}}}}

>>> {"method":"Browser.setWindowBounds","params":{"windowId":2,"bounds":{"width":1200,"height":900,"windowState":"normal"}},"id":7}
    <<< {"id":7,"result":{}}
    <<< {"id":11,"result":{"result":{"type":"undefined"}}}

>>> {"method":"Emulation.setDeviceMetricsOverride","params":{"width":1200,"height":900,"deviceScaleFactor":1,"mobile":false},"id":12}
    <<< {"method":"CSS.mediaQueryResultChanged","params":{}}
    <<< {"id":12,"result":{}}

>>> {"method":"Page.getNavigationHistory","params":{},"id":13}
    <<< {"id":13,"result":{"currentIndex":0,"entries":[{"id":2,"url":"about:blank","userTypedURL":"about:blank","title":"","transitionType":"typed"}]}}

>>> {"method":"Network.setCookie","params":{"name":"tracking","value":"%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D","domain":"127.0.0.1"},"id":14}
    <<< {"id":14,"result":{"success":true}}

>>> {"method":"Page.navigate","params":{"url":"http://127.0.0.1:45389/companyslug-2"},"id":15}
    <<< {"method":"Network.requestWillBeSent","params":{"requestId":"193AC8117FB0EA3616AF24A7BDB479D3","loaderId":"193AC8117FB0EA3616AF24A7BDB479D3","documentURL":"http://127.0.0.1:45389/companyslug-2","request":{"url":"http://127.0.0.1:45389/companyslug-2","method":"GET","headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"no-referrer-when-downgrade"},"timestamp":4239057.925907,"wallTime":1552574369.111231,"initiator":{"type":"other"},"type":"Document","frameId":"4DB9991144C865BC1A76C00D74208FF9","hasUserGesture":false}}
    <<< {"method":"Network.requestWillBeSent","params":{"requestId":"193AC8117FB0EA3616AF24A7BDB479D3","loaderId":"193AC8117FB0EA3616AF24A7BDB479D3","documentURL":"http://127.0.0.1:45389/wayne-enterprises-2","request":{"url":"http://127.0.0.1:45389/wayne-enterprises-2","method":"GET","headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"no-referrer-when-downgrade"},"timestamp":4239058.184127,"wallTime":1552574369.369451,"initiator":{"type":"other"},"redirectResponse":{"url":"http://127.0.0.1:45389/companyslug-2","status":301,"statusText":"Moved Permanently","headers":{"X-Frame-Options":"SAMEORIGIN","X-Xss-Protection":"1; mode=block","X-Content-Type-Options":"nosniff","Location":"http://127.0.0.1:45389/wayne-enterprises-2","Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache","X-Request-Id":"a74f0c18-da7f-4e2a-9606-9f36339af481","X-Runtime":"0.245659","Server":"WEBrick/1.4.2 (Ruby/2.5.3/2018-10-18)","Date":"Thu, 14 Mar 2019 14:39:29 GMT","Content-Length":"117","Connection":"Keep-Alive","Set-Cookie":"locale=de; path=/; expires=Sun, 14 Apr 2019 13:39:29 -0000"},"headersText":"HTTP/1.1 301 Moved Permanently\r\nX-Frame-Options: SAMEORIGIN\r\nX-Xss-Protection: 1; mode=block\r\nX-Content-Type-Options: nosniff\r\nLocation: http://127.0.0.1:45389/wayne-enterprises-2\r\nContent-Type: text/html; charset=utf-8\r\nCache-Control: no-cache\r\nX-Request-Id: a74f0c18-da7f-4e2a-9606-9f36339af481\r\nX-Runtime: 0.245659\r\nServer: WEBrick/1.4.2 (Ruby/2.5.3/2018-10-18)\r\nDate: Thu, 14 Mar 2019 14:39:29 GMT\r\nContent-Length: 117\r\nConnection: Keep-Alive\r\nSet-Cookie: locale=de; path=/; expires=Sun, 14 Apr 2019 13:39:29 -0000\r\n\r\n","mimeType":"text/html","requestHeaders":{"Host":"127.0.0.1:45389","Connection":"keep-alive","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","Accept-Encoding":"gzip, deflate, br","Cookie":"tracking=%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D"},"requestHeadersText":"GET /companyslug-2 HTTP/1.1\r\nHost: 127.0.0.1:45389\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/73.0.3683.75 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nCookie: tracking=%7B%22full%22%3Atrue%2C%22version%22%3A%222018-05-25%22%7D\r\n","connectionReused":false,"connectionId":12,"remoteIPAddress":"127.0.0.1","remotePort":45389,"fromDiskCache":false,"fromServiceWorker":false,"encodedDataLength":536,"timing":{"requestTime":4239057.927005,"proxyStart":-1,"proxyEnd":-1,"dnsStart":0.658,"dnsEnd":0.728,"connectStart":0.728,"connectEnd":0.989,"sslStart":-1,"sslEnd":-1,"workerStart":-1,"workerReady":-1,"sendStart":1.106,"sendEnd":1.162,"pushStart":0,"pushEnd":0,"receiveHeadersEnd":255.864},"protocol":"http/1.1","securityState":"neutral"},"type":"Document","frameId":"4DB9991144C865BC1A76C00D74208FF9","hasUserGesture":false}}

>>> {"method":"Runtime.callFunctionOn","params":{"functionDeclaration":"function() { return window.location.href }","arguments":[],"executionContextId":1},"id":16}

>>> {"method":"Target.detachFromTarget","params":{"sessionId":"10A96BAE9B51A6B48F861DE89CB669A2"},"id":8}
F

Failures:

     Got 0 failures and 3 other errors:

     1.1) Failure/Error: page.visit "/companyslug-#{company.id}"

          Capybara::Cuprite::DeadBrowser:
            Chrome is dead
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:41:in `wait'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:196:in `command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:77:in `visit'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:31:in `visit'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/session.rb:274:in `visit'
          # ./spec/features/company/vcard_spec.rb:35:in `block (2 levels) in <top (required)>'

     1.2) Failure/Error: raise DeadBrowser unless message

          Capybara::Cuprite::DeadBrowser:
            Chrome is dead
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:41:in `wait'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:196:in `command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/runtime.rb:81:in `call'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/runtime.rb:32:in `evaluate'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/frame.rb:19:in `frame_url'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:43:in `frame_url'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:36:in `current_url'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/session.rb:217:in `current_url'
          # /bundle/2.5.3/gems/capybara-screenshot-1.0.14/lib/capybara-screenshot/rspec.rb:55:in `block in after_failed_example'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:351:in `using_session'
          # /bundle/2.5.3/gems/capybara-screenshot-1.0.14/lib/capybara-screenshot/rspec.rb:54:in `after_failed_example'
          # /bundle/2.5.3/gems/capybara-screenshot-1.0.14/lib/capybara-screenshot/rspec.rb:84:in `block (2 levels) in <top (required)>'

     1.3) Failure/Error: @sock.write(data)

          Errno::EPIPE:
            Broken pipe
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/web_socket.rb:61:in `write'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/web_socket.rb:61:in `write'
          # /bundle/2.5.3/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi.rb:228:in `send_frame'
          # /bundle/2.5.3/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi.rb:191:in `frame'
          # /bundle/2.5.3/gems/websocket-driver-0.6.5/lib/websocket/driver/hybi.rb:154:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/web_socket.rb:65:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:56:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:214:in `quit'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:209:in `restart'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:236:in `rescue in command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:232:in `command'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/page.rb:89:in `close'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/targets.rb:80:in `reset'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser.rb:205:in `reset'
          # /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/driver.rb:131:in `reset!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/session.rb:127:in `reset!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:314:in `block in reset_sessions!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:314:in `reverse_each'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara.rb:314:in `reset_sessions!'
          # /bundle/2.5.3/gems/capybara-2.18.0/lib/capybara/rspec.rb:22:in `block (2 levels) in <top (required)>'
          # ------------------
          # --- Caused by: ---
          # Capybara::Cuprite::DeadBrowser:
          #   Chrome is dead
          #   /bundle/2.5.3/gems/cuprite-0.5.0/lib/capybara/cuprite/browser/client.rb:41:in `wait'

Finished in 7.19 seconds (files took 7.42 seconds to load)
1 example, 1 failure`
route commented 5 years ago

@vermaxik that's pretty weird I see no events at all after {"method":"Page.navigate","params":{"url":"http://127.0.0.1:45389/companyslug-2"},"id":15} there must be plenty of them about rendering, network stuff and so on, but it's empty. Do you have frame_url in your test? Could you please show the test itself?

vermaxik commented 5 years ago

No frame_url, and it's really wierd, because yesterday everything works and today with a new version on Chrome everyspecs go down.

Cuprite configration:

Capybara.register_driver :cuprite do |app|
  disable_css_transitions = Rails.root.join('spec', 'support', 'features', 'disable_css_transitions.js')

  Capybara::Cuprite::Driver.new(
    app,
    js_errors: false,
    window_size: [1200, 900],
    browser_options: { 'no-sandbox': nil },
    process_timeout: 6,
    extensions: [disable_css_transitions]
  )

Spec example:


describe 'a vcard is shown' do

  before do
    page.visit "/companyslug-#{company.id}"
  end

  it 'company data is set', js: true do
      within('.body header') do
        expect(page).to have_content 'Wayne Enterprises'
        expect(page).to have_content 'Awesome Street 42, US-12345 Gotham City'
        expect(page).to have_link 'Telephone number'
        expect(page).to have_link 'moc.sesirpretne-enyaw@enyaw_ecurb'
        expect(page).to have_link 'Web Site', href: company.homepage
        expect(page).to have_link 'Company contact'
        expect(page).to have_selector "img[src*='#{company.logo['144x108']}']"
        expect(page).to have_selector "a[href='#contact-inquiry-form-modal']"

        expect(page).not_to have_link('001555123456')
      end

      within('.contact-details') do
        expect(page).to have_selector "a[href='#contact-inquiry-form-modal']"
      end

      link = find('a.visible-xs', visible: false)
      expect(link[:href]).to end_with('#contact-inquiry-form-modal')

      find(".body header a[data-toggle='popover']").trigger('click')
      within('.popover') do
        expect(page).to have_link('001555123456')
      end
    end
  end
end
vermaxik commented 5 years ago

Just tried an unstable version of chrome: Google Chrome 74.0.3729.6 dev and all specs works again without any changes

route commented 5 years ago

@vermaxik Unfortunately I cannot reproduce it on my machine, if you could write an isolated spec I could try to hunt this down. But all looks like the version 73 is buggy indeed

vermaxik commented 5 years ago

@route thanks a lot for helping. We are webmock in this app and I'm assuming this is an issue with Chrom 73. I tried Chrome 73 build in another app without webmock and it works. I'll let know if find a solution to make it workable with webmock.

route commented 5 years ago

Got it thanks!

baxang commented 5 years ago

I had the same issue with Google Chrome 73.0.3683.86. Thanks for the issue report - this saved my time understanding what the cause was.