Skip to content

Commit

Permalink
fixes #1909, do not error timing out when activeElements change out f…
Browse files Browse the repository at this point in the history
…rom under Cypress when window is out of focus

- refactored getting the current focused element to be synchronous
- remove the special handling of force focused or force blurred element
state
- just always use AUT document.activeElement
- clean up tests
  • Loading branch information
brian-mann committed Jun 16, 2018
1 parent 954e0a8 commit 55fcc8e
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 256 deletions.
74 changes: 37 additions & 37 deletions packages/driver/src/cy/commands/actions/click.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -182,44 +182,44 @@ module.exports = (Commands, Cypress, cy, state, config) ->

onReady: ($elToClick, coords) ->
## TODO: get focused through a callback here
cy.now("focused", {log: false, verify: false})
.then ($focused) ->
## record the previously focused element before
## issuing the mousedown because browsers may
## automatically shift the focus to the element
## without firing the focus event
$previouslyFocusedEl = $focused

domEvents.mouseDown = $Mouse.mouseDown($elToClick, coords.fromViewport)

## if mousedown was cancelled then or caused
## our element to be removed from the DOM
## just resolve after mouse down and dont
## send a focus event
if domEvents.mouseDown.preventedDefault or not $dom.isAttached($elToClick)
afterMouseDown($elToClick, coords)
$focused = cy.getFocused()

## record the previously focused element before
## issuing the mousedown because browsers may
## automatically shift the focus to the element
## without firing the focus event
$previouslyFocusedEl = $focused

domEvents.mouseDown = $Mouse.mouseDown($elToClick, coords.fromViewport)

## if mousedown was cancelled then or caused
## our element to be removed from the DOM
## just resolve after mouse down and dont
## send a focus event
if domEvents.mouseDown.preventedDefault or not $dom.isAttached($elToClick)
afterMouseDown($elToClick, coords)
else
## retrieve the first focusable $el in our parent chain
$elToFocus = getFirstFocusableEl($elToClick)

$focused = cy.getFocused()

if shouldFireFocusEvent($focused, $elToFocus)
## if our mousedown went through and
## we are focusing a different element
## dispatch any primed change events
## we have to do this because our blur
## method might not get triggered if
## our window is in focus since the
## browser may fire blur events naturally
$actionability.dispatchPrimedChangeEvents(state)

## send in a focus event!
cy.now("focus", $elToFocus, {$el: $elToFocus, error: false, verify: false, log: false})
.then ->
afterMouseDown($elToClick, coords)
else
## retrieve the first focusable $el in our parent chain
$elToFocus = getFirstFocusableEl($elToClick)

cy.now("focused", {log: false, verify: false})
.then ($focused) =>
if shouldFireFocusEvent($focused, $elToFocus)
## if our mousedown went through and
## we are focusing a different element
## dispatch any primed change events
## we have to do this because our blur
## method might not get triggered if
## our window is in focus since the
## browser may fire blur events naturally
$actionability.dispatchPrimedChangeEvents(state)

## send in a focus event!
cy.now("focus", $elToFocus, {$el: $elToFocus, error: false, verify: false, log: false})
.then ->
afterMouseDown($elToClick, coords)
else
afterMouseDown($elToClick, coords)
afterMouseDown($elToClick, coords)
})
.catch (err) ->
## snapshot only on click failure
Expand Down
214 changes: 107 additions & 107 deletions packages/driver/src/cy/commands/actions/focus.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->

focused = ->
hasFocused = true

## set this back to null unless we are
## force focused ourselves during this command
forceFocusedEl = cy.state("forceFocusedEl")
if forceFocusedEl isnt options.$el.get(0)
cy.state("forceFocusedEl", null)


cleanup()

cy.timeout($actionability.delay, true)
Expand All @@ -83,6 +77,10 @@ module.exports = (Commands, Cypress, cy, state, config) ->

options.$el.on("focus", focused)

## store the currently focused item
## for later use if necessary to simulate events
$focused = cy.getFocused()

## does this synchronously fire?
## if it does we don't need this
## lower promise
Expand All @@ -98,8 +96,6 @@ module.exports = (Commands, Cypress, cy, state, config) ->
simulate = ->
win = cy.state("window")

cy.state("forceFocusedEl", options.$el.get(0))

## todo handle relatedTarget's per the spec
focusinEvt = new FocusEvent "focusin", {
bubbles: true
Expand All @@ -120,21 +116,25 @@ module.exports = (Commands, Cypress, cy, state, config) ->
options.$el.get(0).dispatchEvent(focusEvt)
options.$el.get(0).dispatchEvent(focusinEvt)

cy.now("focused", {log: false, verify: false}).then ($focused) ->
## only blur if we have a focused element AND its not
## currently ourselves!
if $focused and $focused.get(0) isnt options.$el.get(0)

cy.now("blur", $focused, {$el: $focused, error: false, verify: false, log: false})
.then ->
simulate()
else
## only blur if we have a focused element AND its not
## currently ourselves!
if $focused and $focused.get(0) isnt options.$el.get(0)
## TODO: oh god... so bad, please fixme
cy.now("blur", $focused, {
$focused
$el: $focused,
error: false,
verify: false,
log: false
})
.then ->
simulate()
else
simulate()

## need to catch potential errors from blur
## here and reject the promise
.catch (err) ->
reject(err)
## need to catch potential errors from blur
## here and reject the promise
.catch(reject)

promise
.timeout(timeout)
Expand All @@ -157,11 +157,14 @@ module.exports = (Commands, Cypress, cy, state, config) ->
## but allow them to be silenced
_.defaults(options, {
$el: subject
$focused: cy.getFocused()
error: true
log: true
verify: true
force: false
})

{ $el, $focused } = options

if isWin = $dom.isWindow(options.$el)
## get this into a jquery object
Expand All @@ -185,108 +188,105 @@ module.exports = (Commands, Cypress, cy, state, config) ->
args: { num }
})

cy.now("focused", {log: false, verify: false}).then ($focused) ->
## if we haven't forced the blur, and we don't currently
## have a focused element OR we aren't the window then error
if (options.force isnt true) and (not $focused) and (not isWin)
return if options.error is false

$utils.throwErrByPath("blur.no_focused_element", { onFail: options._log })

## if we're currently window dont check for the wrong
## focused element because window will not show up
## as $focused
if (options.force isnt true) and (not isWin) and (
options.$el.get(0) isnt $focused.get(0)
)
return if options.error is false

node = $dom.stringify($focused)
$utils.throwErrByPath("blur.wrong_focused_element", {
onFail: options._log
args: { node }
})

timeout = cy.timeout() * .90

cleanup = null
hasBlurred = false
## if we haven't forced the blur, and we don't currently
## have a focused element OR we aren't the window then error
if (options.force isnt true) and (not $focused) and (not isWin)
return if options.error is false

promise = new Promise (resolve) ->
## we need to bind to the blur event here
## because some browsers will not ever fire
## the blur event if the window itself is not
## currently focused. so we have to tell our users
## to do just that!
cleanup = ->
options.$el.off("blur", blurred)
$utils.throwErrByPath("blur.no_focused_element", { onFail: options._log })

blurred = ->
hasBlurred = true
## if we're currently window dont check for the wrong
## focused element because window will not show up
## as $focused
if (options.force isnt true) and (not isWin) and (
options.$el.get(0) isnt $focused.get(0)
)
return if options.error is false

## set this back to null unless we are
## force blurring ourselves during this command
blacklistFocusedEl = cy.state("blacklistFocusedEl")
cy.state("blacklistFocusedEl", null) unless blacklistFocusedEl is options.$el.get(0)
node = $dom.stringify($focused)
$utils.throwErrByPath("blur.wrong_focused_element", {
onFail: options._log
args: { node }
})

cleanup()
timeout = cy.timeout() * .90

cy.timeout($actionability.delay, true)
cleanup = null
hasBlurred = false

Promise
.delay($actionability.delay)
.then(resolve)
promise = new Promise (resolve) ->
## we need to bind to the blur event here
## because some browsers will not ever fire
## the blur event if the window itself is not
## currently focused. so we have to tell our users
## to do just that!
cleanup = ->
options.$el.off("blur", blurred)

options.$el.on("blur", blurred)
blurred = ->
hasBlurred = true

## for simplicity we allow change events
## to be triggered by a manual blur
$actionability.dispatchPrimedChangeEvents(state)
cleanup()

options.$el.get(0).blur()
cy.timeout($actionability.delay, true)

Promise
.resolve(null)
.then ->
## fallback if our blur event never fires
## to simulate the blur + focusout
return if hasBlurred
.delay($actionability.delay)
.then(resolve)

win = cy.state("window")
options.$el.on("blur", blurred)

cy.state("blacklistFocusedEl", options.$el.get(0))
## for simplicity we allow change events
## to be triggered by a manual blur
$actionability.dispatchPrimedChangeEvents(state)

## todo handle relatedTarget's per the spec
focusoutEvt = new FocusEvent "focusout", {
bubbles: true
cancelable: false
view: win
relatedTarget: null
}
## NOTE: we could likely check document.hasFocus()
## here and expect that blur events are not fired
## when the window is not in focus. that would prevent
## us from making blur + focus async since we wait
## immediately below
options.$el.get(0).blur()

blurEvt = new FocusEvent "blur", {
bubble: false
cancelable: false
view: win
relatedTarget: null
}

options.$el.get(0).dispatchEvent(blurEvt)
options.$el.get(0).dispatchEvent(focusoutEvt)
Promise
.resolve(null)
.then ->
## fallback if our blur event never fires
## to simulate the blur + focusout
return if hasBlurred

win = cy.state("window")

## todo handle relatedTarget's per the spec
focusoutEvt = new FocusEvent "focusout", {
bubbles: true
cancelable: false
view: win
relatedTarget: null
}

blurEvt = new FocusEvent "blur", {
bubble: false
cancelable: false
view: win
relatedTarget: null
}

options.$el.get(0).dispatchEvent(blurEvt)
options.$el.get(0).dispatchEvent(focusoutEvt)

promise
.timeout(timeout)
.catch Promise.TimeoutError, (err) ->
cleanup()
promise
.timeout(timeout)
.catch Promise.TimeoutError, (err) ->
cleanup()

return if options.error is false
return if options.error is false

$utils.throwErrByPath "blur.timed_out", { onFail: command }
.then ->
return options.$el if options.verify is false
$utils.throwErrByPath "blur.timed_out", { onFail: command }
.then ->
return options.$el if options.verify is false

do verifyAssertions = ->
cy.verifyUpcomingAssertions(options.$el, options, {
onRetry: verifyAssertions
})
do verifyAssertions = ->
cy.verifyUpcomingAssertions(options.$el, options, {
onRetry: verifyAssertions
})
})
Loading

0 comments on commit 55fcc8e

Please sign in to comment.