lib/capybara/poltergeist/client/browser.coffee in poltergeist-1.5.1 vs lib/capybara/poltergeist/client/browser.coffee in poltergeist-1.6.0
- old
+ new
@@ -1,118 +1,103 @@
class Poltergeist.Browser
constructor: (@owner, width, height) ->
@width = width || 1024
@height = height || 768
- @state = 'default'
- @page_stack = []
- @page_id = 0
+ @pages = []
@js_errors = true
@_debug = false
+ @_counter = 0
this.resetPage()
resetPage: ->
+ [@_counter, @pages] = [0, []]
+
if @page?
- @page.release()
+ unless @page.closed
+ @page.clearLocalStorage() if @page.currentUrl() != 'about:blank'
+ @page.release()
phantom.clearCookies()
- @page = new Poltergeist.WebPage
+ @page = @currentPage = new Poltergeist.WebPage
@page.setViewportSize(width: @width, height: @height)
+ @page.handle = "#{@_counter++}"
+ @pages.push(@page)
- @page.onLoadStarted = =>
- this.setState 'loading' if @state == 'mouse_event'
+ @page.onPageCreated = (newPage) =>
+ page = new Poltergeist.WebPage(newPage)
+ page.handle = "#{@_counter++}"
+ @pages.push(page)
- @page.onNavigationRequested = (url, navigation) =>
- this.setState 'loading' if @state == 'mouse_event' && navigation == 'FormSubmitted'
+ getPageByHandle: (handle) ->
+ @pages.filter((p) -> !p.closed && p.handle == handle)[0]
- @page.onLoadFinished = (status) =>
- if @state == 'loading'
- this.sendResponse(status: status, position: @last_mouse_event)
- this.setState 'default'
- else if @state == 'awaiting_frame_load'
- this.sendResponse(true)
- this.setState 'default'
-
- @page.onInitialized = =>
- @page_id += 1
-
- @page.onPageCreated = (sub_page) =>
- if @state == 'awaiting_sub_page'
- name = @page_name
- @page_name = null
-
- this.setState 'default'
-
- # At this point subpage isn't fully initialized, so we can't check
- # its name. Instead, we just schedule another attempt to push the
- # window.
- setTimeout((=> this.push_window(name)), 0)
-
runCommand: (name, args) ->
- this.setState "default"
+ @currentPage.state = 'default'
this[name].apply(this, args)
debug: (message) ->
if @_debug
console.log "poltergeist [#{new Date().getTime()}] #{message}"
- setState: (state) ->
- return if @state == state
- this.debug "state #{@state} -> #{state}"
- @state = state
-
sendResponse: (response) ->
- errors = @page.errors()
- @page.clearErrors()
+ errors = @currentPage.errors
+ @currentPage.clearErrors()
if errors.length > 0 && @js_errors
@owner.sendError(new Poltergeist.JavascriptError(errors))
else
@owner.sendResponse(response)
add_extension: (extension) ->
- @page.injectExtension extension
+ @currentPage.injectExtension extension
this.sendResponse 'success'
node: (page_id, id) ->
- if page_id == @page_id
- @page.get(id)
+ if @currentPage.id == page_id
+ @currentPage.get(id)
else
throw new Poltergeist.ObsoleteNode
visit: (url) ->
- this.setState 'loading'
+ @currentPage.state = 'loading'
# Prevent firing `page.onInitialized` event twice. Calling currentUrl
# method before page is actually opened fires this event for the first time.
# The second time will be in the right place after `page.open`
- prev_url = if @page.source() is null then 'about:blank' else @page.currentUrl()
+ prevUrl = if @currentPage.source is null then 'about:blank' else @currentPage.currentUrl()
- @page.open(url)
+ @currentPage.open(url)
- if /#/.test(url) && prev_url.split('#')[0] == url.split('#')[0]
- # hashchange occurred, so there will be no onLoadFinished
- this.setState 'default'
- this.sendResponse 'success'
+ if /#/.test(url) && prevUrl.split('#')[0] == url.split('#')[0]
+ # Hash change occurred, so there will be no onLoadFinished
+ @currentPage.state = 'default'
+ this.sendResponse(status: 'success')
+ else
+ @currentPage.waitState 'default', =>
+ if @currentPage.statusCode == null && @currentPage.status == 'fail'
+ @owner.sendError(new Poltergeist.StatusFailError)
+ else
+ this.sendResponse(status: @currentPage.status)
current_url: ->
- this.sendResponse @page.currentUrl()
+ this.sendResponse @currentPage.currentUrl()
status_code: ->
- this.sendResponse @page.statusCode()
+ this.sendResponse @currentPage.statusCode
body: ->
- this.sendResponse @page.content()
+ this.sendResponse @currentPage.content()
source: ->
- this.sendResponse @page.source()
+ this.sendResponse @currentPage.source
title: ->
- this.sendResponse @page.title()
+ this.sendResponse @currentPage.title()
find: (method, selector) ->
- this.sendResponse(page_id: @page_id, ids: @page.find(method, selector))
+ this.sendResponse(page_id: @currentPage.id, ids: @currentPage.find(method, selector))
find_within: (page_id, id, method, selector) ->
this.sendResponse this.node(page_id, id).find(method, selector)
all_text: (page_id, id) ->
@@ -125,10 +110,16 @@
this.sendResponse this.node(page_id, id).deleteText()
attribute: (page_id, id, name) ->
this.sendResponse this.node(page_id, id).getAttribute(name)
+ attributes: (page_id, id, name) ->
+ this.sendResponse this.node(page_id, id).getAttributes()
+
+ parents: (page_id, id) ->
+ this.sendResponse this.node(page_id, id).parentIds()
+
value: (page_id, id) ->
this.sendResponse this.node(page_id, id).value()
set: (page_id, id, value) ->
this.node(page_id, id).set(value)
@@ -138,13 +129,13 @@
# so we have to add an attribute to the element to identify it, then remove it
# afterwards.
select_file: (page_id, id, value) ->
node = this.node(page_id, id)
- @page.beforeUpload(node.id)
- @page.uploadFile('[_poltergeist_selected]', value)
- @page.afterUpload(node.id)
+ @currentPage.beforeUpload(node.id)
+ @currentPage.uploadFile('[_poltergeist_selected]', value)
+ @currentPage.afterUpload(node.id)
this.sendResponse(true)
select: (page_id, id, value) ->
this.sendResponse this.node(page_id, id).select(value)
@@ -157,84 +148,110 @@
disabled: (page_id, id) ->
this.sendResponse this.node(page_id, id).isDisabled()
evaluate: (script) ->
- this.sendResponse @page.evaluate("function() { return #{script} }")
+ this.sendResponse @currentPage.evaluate("function() { return #{script} }")
execute: (script) ->
- @page.execute("function() { #{script} }")
+ @currentPage.execute("function() { #{script} }")
this.sendResponse(true)
+ frameUrl: (frame_name) ->
+ @currentPage.frameUrl(frame_name)
+
push_frame: (name, timeout = new Date().getTime() + 2000) ->
- if @page.pushFrame(name)
- if @page.currentUrl() == 'about:blank'
- this.setState 'awaiting_frame_load'
+ if @frameUrl(name) in @currentPage.blockedUrls()
+ this.sendResponse(true)
+ else if @currentPage.pushFrame(name)
+ if @currentPage.currentUrl() == 'about:blank'
+ @currentPage.state = 'awaiting_frame_load'
+ @currentPage.waitState 'default', =>
+ this.sendResponse(true)
else
this.sendResponse(true)
else
if new Date().getTime() < timeout
setTimeout((=> this.push_frame(name, timeout)), 50)
else
@owner.sendError(new Poltergeist.FrameNotFound(name))
- pages: ->
- this.sendResponse(@page.pages())
-
pop_frame: ->
- this.sendResponse(@page.popFrame())
+ this.sendResponse(@currentPage.popFrame())
- push_window: (name) ->
- sub_page = @page.getPage(name)
+ window_handles: ->
+ handles = @pages.filter((p) -> !p.closed).map((p) -> p.handle)
+ this.sendResponse(handles)
- if sub_page
- if sub_page.currentUrl() == 'about:blank'
- sub_page.onLoadFinished = =>
- sub_page.onLoadFinished = null
- this.push_window(name)
+ window_handle: (name = null) ->
+ handle = if name
+ page = @pages.filter((p) -> !p.closed && p.windowName() == name)[0]
+ if page then page.handle else null
+ else
+ @currentPage.handle
+
+ this.sendResponse(handle)
+
+ switch_to_window: (handle) ->
+ page = @getPageByHandle(handle)
+ if page
+ if page != @currentPage
+ page.waitState 'default', =>
+ @currentPage = page
+ this.sendResponse(true)
else
- @page_stack.push(@page)
- @page = sub_page
- @page_id += 1
this.sendResponse(true)
else
- @page_name = name
- this.setState 'awaiting_sub_page'
+ throw new Poltergeist.NoSuchWindowError
- pop_window: ->
- prev_page = @page_stack.pop()
- @page = prev_page if prev_page
+ open_new_window: ->
+ this.execute 'window.open()'
this.sendResponse(true)
+ close_window: (handle) ->
+ page = @getPageByHandle(handle)
+ if page
+ page.release()
+ this.sendResponse(true)
+ else
+ this.sendResponse(false)
+
mouse_event: (page_id, id, name) ->
# Get the node before changing state, in case there is an exception
node = this.node(page_id, id)
# If the event triggers onNavigationRequested, we will transition to the 'loading'
# state and wait for onLoadFinished before sending a response.
- this.setState 'mouse_event'
+ @currentPage.state = 'mouse_event'
@last_mouse_event = node.mouseEvent(name)
setTimeout =>
- if @state != 'loading'
- this.setState 'default'
- this.sendResponse(@last_mouse_event)
+ # If the state is still the same then navigation event won't happen
+ if @currentPage.state == 'mouse_event'
+ @currentPage.state = 'default'
+ this.sendResponse(position: @last_mouse_event)
+ else
+ @currentPage.waitState 'default', =>
+ this.sendResponse(position: @last_mouse_event)
, 5
click: (page_id, id) ->
this.mouse_event page_id, id, 'click'
+ right_click: (page_id, id) ->
+ this.mouse_event page_id, id, 'rightclick'
+
double_click: (page_id, id) ->
this.mouse_event page_id, id, 'doubleclick'
hover: (page_id, id) ->
this.mouse_event page_id, id, 'mousemove'
click_coordinates: (x, y) ->
- @page.sendEvent('click', x, y)
- this.sendResponse({ click: { x: x, y: y } })
+ @currentPage.sendEvent('click', x, y)
+ this.sendResponse(click: { x: x, y: y })
drag: (page_id, id, other_id) ->
this.node(page_id, id).dragTo this.node(page_id, other_id)
this.sendResponse(true)
@@ -248,105 +265,117 @@
reset: ->
this.resetPage()
this.sendResponse(true)
scroll_to: (left, top) ->
- @page.setScrollPosition(left: left, top: top)
+ @currentPage.setScrollPosition(left: left, top: top)
this.sendResponse(true)
send_keys: (page_id, id, keys) ->
+ target = this.node(page_id, id)
+
# Programmatically generated focus doesn't work for `sendKeys`.
# That's why we need something more realistic like user behavior.
- this.node(page_id, id).mouseEvent('click')
+ if !target.containsSelection()
+ target.mouseEvent('click')
+
for sequence in keys
- key = if sequence.key? then @page.native.event.key[sequence.key] else sequence
- @page.sendEvent('keypress', key)
+ key = if sequence.key? then @currentPage.keyCode(sequence.key) else sequence
+ @currentPage.sendEvent('keypress', key)
this.sendResponse(true)
render_base64: (format, full, selector = null)->
this.set_clip_rect(full, selector)
- encoded_image = @page.renderBase64(format)
+ encoded_image = @currentPage.renderBase64(format)
this.sendResponse(encoded_image)
render: (path, full, selector = null) ->
dimensions = this.set_clip_rect(full, selector)
- @page.setScrollPosition(left: 0, top: 0)
- @page.render(path)
- @page.setScrollPosition(left: dimensions.left, top: dimensions.top)
+ @currentPage.setScrollPosition(left: 0, top: 0)
+ @currentPage.render(path)
+ @currentPage.setScrollPosition(left: dimensions.left, top: dimensions.top)
this.sendResponse(true)
set_clip_rect: (full, selector) ->
- dimensions = @page.validatedDimensions()
+ dimensions = @currentPage.validatedDimensions()
[document, viewport] = [dimensions.document, dimensions.viewport]
rect = if full
left: 0, top: 0, width: document.width, height: document.height
else
if selector?
- @page.elementBounds(selector)
+ @currentPage.elementBounds(selector)
else
left: 0, top: 0, width: viewport.width, height: viewport.height
- @page.setClipRect(rect)
+ @currentPage.setClipRect(rect)
dimensions
set_paper_size: (size) ->
- @page.setPaperSize(size)
+ @currentPage.setPaperSize(size)
this.sendResponse(true)
+ set_zoom_factor: (zoom_factor) ->
+ @currentPage.setZoomFactor(zoom_factor)
+ this.sendResponse(true)
+
resize: (width, height) ->
- @page.setViewportSize(width: width, height: height)
+ @currentPage.setViewportSize(width: width, height: height)
this.sendResponse(true)
network_traffic: ->
- this.sendResponse(@page.networkTraffic())
+ this.sendResponse(@currentPage.networkTraffic())
clear_network_traffic: ->
- @page.clearNetworkTraffic()
+ @currentPage.clearNetworkTraffic()
this.sendResponse(true)
get_headers: ->
- this.sendResponse(@page.getCustomHeaders())
+ this.sendResponse(@currentPage.getCustomHeaders())
set_headers: (headers) ->
# Workaround for https://code.google.com/p/phantomjs/issues/detail?id=745
- @page.setUserAgent(headers['User-Agent']) if headers['User-Agent']
- @page.setCustomHeaders(headers)
+ @currentPage.setUserAgent(headers['User-Agent']) if headers['User-Agent']
+ @currentPage.setCustomHeaders(headers)
this.sendResponse(true)
add_headers: (headers) ->
- allHeaders = @page.getCustomHeaders()
+ allHeaders = @currentPage.getCustomHeaders()
for name, value of headers
allHeaders[name] = value
this.set_headers(allHeaders)
add_header: (header, permanent) ->
- @page.addTempHeader(header) unless permanent
+ @currentPage.addTempHeader(header) unless permanent
this.add_headers(header)
response_headers: ->
- this.sendResponse(@page.responseHeaders())
+ this.sendResponse(@currentPage.responseHeaders())
cookies: ->
- this.sendResponse(@page.cookies())
+ this.sendResponse(@currentPage.cookies())
# We're using phantom.addCookie so that cookies can be set
# before the first page load has taken place.
set_cookie: (cookie) ->
phantom.addCookie(cookie)
this.sendResponse(true)
remove_cookie: (name) ->
- @page.deleteCookie(name)
+ @currentPage.deleteCookie(name)
this.sendResponse(true)
+ clear_cookies: () ->
+ phantom.clearCookies()
+ this.sendResponse(true)
+
cookies_enabled: (flag) ->
phantom.cookiesEnabled = flag
this.sendResponse(true)
set_http_auth: (user, password) ->
- @page.setHttpAuth(user, password)
+ @currentPage.setHttpAuth(user, password)
this.sendResponse(true)
set_js_errors: (value) ->
@js_errors = value
this.sendResponse(true)
@@ -364,11 +393,25 @@
# This command is purely for testing error handling
browser_error: ->
throw new Error('zomg')
go_back: ->
- this.page.goBack() if this.page.canGoBack
- this.sendResponse(true)
+ if @currentPage.canGoBack
+ @currentPage.state = 'loading'
+ @currentPage.goBack()
+ @currentPage.waitState 'default', =>
+ this.sendResponse(true)
+ else
+ this.sendResponse(false)
go_forward: ->
- this.page.goForward() if this.page.canGoForward
- this.sendResponse(true)
+ if @currentPage.canGoForward
+ @currentPage.state = 'loading'
+ @currentPage.goForward()
+ @currentPage.waitState 'default', =>
+ this.sendResponse(true)
+ else
+ this.sendResponse(false)
+
+ set_url_blacklist: ->
+ @currentPage.urlBlacklist = Array.prototype.slice.call(arguments)
+ @sendResponse(true)