83

Perhaps this isn't actually the issue I'm experiencing, but it seems that when I "click_link" a link with target="_blank", the session keeps the focus on the current window.

So I either want to be able to switch to the new window, or to ignore the _blank attribute - essentially, I just want it to actually go to the page indicated by the link so I can make sure it's the right page.

I use the webkit and selenium drivers.


I submitted my findings thus far below. A more thorough answer is much appreciated.

Also, this only works with selenium - the equivalent for the webkit driver (or pointing out where I could discover it myself) would be much appreciated.

GlyphGryph
  • 4,714
  • 4
  • 32
  • 43

15 Answers15

96

Capybara >= 2.3 includes the new window management API. It can be used like:

new_window = window_opened_by { click_link 'Something' }
within_window new_window do
  # code
end
Andrei Botalov
  • 20,686
  • 11
  • 89
  • 123
54

This solution only works for the Selenium driver

All open windows are stores in Selenium's

response.driver.browser.window_handles

Which seems to be an array. The last item is always the window that was most recently opened, meaning you can do the following to switch to it.

Within a block:

new_window=page.driver.browser.window_handles.last 
page.within_window new_window do
  #code
end

Simply refocus for current session:

session.driver.browser.switch_to.window(page.driver.browser.window_handles.last)

Referenced on the capybara issues page: https://github.com/jnicklas/capybara/issues/173

More details on Selenium's window switching capabilities: http://qastuffs.blogspot.com/2010/10/testing-pop-up-windows-using-selenium.html

GlyphGryph
  • 4,714
  • 4
  • 32
  • 43
  • 4
    This is great, but the second example is a bit misleading. There is no difference between `session` and `page`, so `session.driver.browser` and `page.driver.browser` actually refer to the same thing. That messed me up while trying to adapt this to a different context, accessing Capybara through a different library, and not using the DSL. I had to read the Capybara DSL code to figure out that to access 'page`, I just need to access the session. – Steve Jorgensen Jul 09 '12 at 08:05
  • @glyphgryph boy I love when solutions just work with a copy & paste, sans all the extra waste. – nfriend21 Oct 29 '13 at 11:30
  • 3
    It's not only for Selenium: `within_window(page.driver.window_handles.last)` works for me with **capybara-webkit**. – Tony - Currentuser.io May 02 '14 at 14:54
  • 2
    As mentioned below, Capybara has a new syntax for dealing with windows: https://github.com/jnicklas/capybara#working-with-windows – Sarah Apr 01 '15 at 23:21
  • 1
    This method is deprecated in capybara `DEPRECATION WARNING: Passing string argument to #within_window is deprecated.` – Aravin Nov 24 '15 at 11:31
11

This is now working with Poltergeist. Although window_handles is still not implemented (you need a window name, i.e. via a JavaScript popup):

within_window 'other_window' do
  current_url.should match /example.com/
end

Edit: Per comment below, Poltergeist now implements window_handles since version 1.4.0.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
  • 3
    I just tried `page.within_window page.driver.browser.window_handles.last do ... ` and it appears to work as expected in Poltergeist. No need to give a window name. So this answer might need to be updated. – thom_nic Oct 27 '14 at 20:11
  • 1
    Capybara 2.3 deprecates calling `within_window` with a String: `DEPRECATION WARNING: Passing string argument to #within_window is deprecated. Pass window object or lambda.` The method documented by Andrey Botalov fixes this deprecation. – Dave Schweisguth Jun 30 '15 at 12:12
11

Capybara provides some methods to ease finding and switching windows:

facebook_window = window_opened_by do
  click_button 'Like'
end
within_window facebook_window do
  find('#login_email').set('a@example.com')
  find('#login_password').set('qwerty')
  click_button 'Submit'
end

More details here: Capybara documentation

Aravin
  • 6,605
  • 5
  • 42
  • 58
9

I know this is old post, but for what its worth in capybara 2.4.4

within_window(switch_to_window(windows.last)) do 
    # in my case assert redirected url from a prior click action
    expect(current_url).to eq(redirect['url'])
end
bcar
  • 815
  • 9
  • 19
7

Seems like it is not possible with capybara-webkit right now: https://github.com/thoughtbot/capybara-webkit/issues/271

:-(

At the same time https://github.com/thoughtbot/capybara-webkit/issues/129 claims it is possible to switch windows with within_window.

Also https://github.com/thoughtbot/capybara-webkit/issues/47 suggests that page.driver.browser.switch_to().window(page.driver.browser.window_handles.last) works. Ah well, on to code reading.

The code at https://github.com/thoughtbot/capybara-webkit/blob/master/lib/capybara/webkit/browser.rb at least has some references that suggest that the API that works for webdriver / firefox is also working for webkit.

user132837
  • 944
  • 7
  • 5
6

Now within_window implemented for capybara-webkit http://github.com/thoughtbot/capybara-webkit/pull/314 and here you can see how to use it http://github.com/mhoran/capybara-webkit-demo

E-Hauler
  • 136
  • 1
  • 6
6

As of May 2014 the following code works on capybara-webkit

 within_window(page.driver.browser.window_handles.last) do
   expect(current_url).to eq('http://www.example.com/')
 end
thekindofme
  • 3,846
  • 26
  • 29
  • `page.driver.brower.window_handles.las` is now deprecated. Use `windows.last`. See https://github.com/thoughtbot/capybara-webkit/issues/650 – smcgregor May 02 '16 at 01:43
6

To explicitly change window, you use switch_to_window

  def terms_of_use
    terms_window = window_opened_by do
      click_link(@terms_link)
    end
    switch_to_window(terms_window)
  end

An after that method browser will work in the new page, instead of wrap everything in a within_window block

G. I. Joe
  • 1,585
  • 17
  • 21
4

This works for me in capybara-webkit:

within_window(windows.last) do
  # code here
end

(I'm using capybara 2.4.1 and capybara-webkit 1.3.0)

lavaturtle
  • 2,521
  • 2
  • 16
  • 11
4

You can pass a name, url or title of the window also (But now its dipricated)

  let(:product) { create :product }

  it 'tests' do
    visit products_path
    click_link(product.id)

    within_window(product_path(product)) do
      expect(page).to have_content(product.title)
    end
  end

You can pass a labda or a proc also

within_window(->{ page.title == 'Page title' }) do
  click_button 'Submit'
end

wish it stretches the method usage to more clearly understaing

itsnikolay
  • 17,415
  • 4
  • 65
  • 64
3

I had this issue when opening links in an gmail window: I fixed it like this:

Given /^(?:|I )click the "([^"]*)" link in email message$/ do |field|

  # var alllinks = document.getElementsByTagName("a");
  # for (alllinksi=0; alllinksi<alllinks.length; alllinksi++) {
  #   alllinks[alllinksi].removeAttribute("target");
  # }

  page.execute_script('var alllinks = document.getElementsByTagName("a"); for (alllinksi=0; alllinksi<alllinks.length; alllinksi++) { alllinks[alllinksi].removeAttribute("target"); }')

  within(:css, "div.msg") do
    click_link link_text
  end

end
complistic
  • 2,610
  • 1
  • 29
  • 37
  • BTW, instead of comments you could use heredoc syntax: `page.execute_script <<-JS var alllinks = document.getElementsByTagName("a"); for (alllinksi=0; alllinksi – David Feb 04 '16 at 18:05
3

The best idea is to update capybara to the latests version (2.4.1) and just use windows.last because page.driver.browser.window_handles is deprecated.

Adam Piotrowski
  • 674
  • 1
  • 7
  • 15
0

The main implementation (window_opened_by) raises an error for me:

*** Capybara::WindowError Exception: block passed to #window_opened_by opened 0 windows instead of 1

So, I resolve it by this solution:

new_window = open_new_window

within_window new_window do
  visit(click_link 'Something')
end

page.driver.browser.window_handles
# => ["CDwindow-F7EF6D3C12B68D6B6A3DFC69C2790718", "CDwindow-9A026DEC65C3C031AF7D2BA12F28ADC7"]
Alex Strizhak
  • 910
  • 1
  • 12
  • 22
0

I personally like the following approach since it works correctly regardless of JS being enabled or not.

My Spec:

  it "shows as expected" do
    visit my_path

    # ...test my non-JS stuff in the current tab

    switch_to_new_tab

    # ...test my non-JS stuff in the new tab
    
    # ...keep switching to new tabs as much as necessary
  end 

  # OR

  it "shows as expected", js: true do
    visit my_path

    # ...test my non-JS stuff in the current tab
    # ...also test my JS stuff in the current tab

    switch_to_new_tab

    # ...test my non-JS stuff in the new tab
    # ...also test my JS stuff in the new tab

    # ...keep switching to new tabs as much as necessary
  end 

Test helpers:

  def switch_to_new_tab
    current_browser = page.driver.browser

    if js_enabled?
      current_browser.switch_to.window(current_browser.window_handles.last)
    else
      visit current_browser.last_request.fullpath
    end
  end

  def js_enabled?
    Capybara.current_driver == Capybara.javascript_driver
  end
Andres Leon
  • 131
  • 1
  • 8