Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

has_text? and invisible content #844

Closed
jonleighton opened this Issue · 37 comments
@jonleighton

Capybara master has a new has_text? matcher which excludes invisible content such as <title> elements and display: none elements.

Its implementation uses the Node#text method, so the implication is that the behaviour of Node#text has now changed from returning all text to only returning visible text. This presents two problems:

  • Users will have trouble upgrading if they are using Node#text directly, or has_content? (which is now just an alias of has_text?)
  • There is no API for getting e.g. the text of the <title> element. This could be a valid user requirement and was previously supported by the #text method.

I propose adding a #visible_text method and using that to implement has_text?, leaving the implementations of #text and #has_content? as they are in 1.1. (#has_content? can be deprecated but I believe #text remains useful for checking e.g. <title> text.) WDYT?

Related: #522

cc @mkdynamic

@jonleighton jonleighton referenced this issue in teampoltergeist/poltergeist
Closed

Invisible elements being returned #173

@ugisozols

+1 for not being able to query <title> after upgrading to 2.0.

find("title").should have_content("some text") now fails.

@demetrios

+1 for troubles with <title> in 2.0

it { should have_selector('title', text: 'Some Title') }

will fail on <head><title>Some Title</title></head>

@jnicklas
Owner

Wouldn't you guys rather have an explicit API for testing the page title, rather than changing the behaviour of #text just for this? That seems much more sensible to me.

@demetrios

OK with me...just point me to it.

@jnicklas
Owner

There isn't one, because no one has bothered to build one.

@demetrios

Wish I could help...but I've never worked on a gem and would only cause more problems...

@Mic92

Maybe there should be a way to get content of every tag, even if it is not visible.

@Mic92

I think the problem is that the railstutorial makes heavily use of this feature (check the title) and people tend to copy these examples.

@demetrios

Yup, that's my problem. I learned TDD & RSpec from railstutorial.org and as such most of my projects carry with them some basic structure gleaned from there. After upgrading projects to 2.0 my title specs are failing everywhere.

FWIW, it's a little confusing to a relative newb like me when this is happening:

  it { should have_selector('title', text: 'Some Title') } # <= fails
  it { should have_selector('title') }                     # <= passes
  it { should have_text('Some Title') }                    # <= passes

…and according to http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Matchers#has_selector%3F-instance_method we should be able to find elements containing text:. Now maybe those docs are for 1.x…but that's not clear either...to me anyway.

If there's an alternative way to easily test the title in a feature spec let me know and I'll go away…leaving you all to continue your great work!

@anilali

+1 for not being able to query after upgrading to 2.0.</p>

@jonleighton

Adding a #has_title? matcher sounds like a decent solution to me. I'll tweet about in and see if I can get someone to step up to do the work.

@gucki

IMO the behavior of 2.0 is completely broken. In 1.1.4 page.find("title").text returns the text, in 2.0 it returns an empty string. Returning an empty string is just wrong when the element does contain text.

@mkdynamic

My original reason for wanting the has_text matcher (#522) was to be able to test specifically for 'text visible to the user'.

The title tag is visible text, it's shown in the browser chrome.

Given that, I think it would be ideal if the title text was included.

@jnicklas
Owner

@gucki: the title tag is an element and it is decidedly not visible by default. Try adding this CSS selector to a page:

head, title { display: block }

Boom, there is the title element and it is quite distinct from the title shown in the browser chrome. This element is not visible, and if we follow the logic that the text of invisible elements is not shown, then it makes sense that the title element has no text. I agree that that's a matter of debate and discussion, but I wouldn't go so far as to say that the current behaviour is broken. It's just a different interpretation.

I think the solution to this problem is two-fold:

  1. Add some kind of other method which returns the text regardless of if it's visible or not. This could be an option to #text or a separate method.
  2. Add an official API for retrieving the title of the page and querying it. Some work on this has already been done on this and I will take a closer look as soon as I have the time.
@mkdynamic

@jnicklas Well, it is visible :)

I do think it makes more semantic sense for has_text to include text from the title element. Since the whole idea behind has_text is to move away from the logical model of XML's notion of text and towards the notion of human readable text.

I think your suggested solution for #1 and #2 are a good idea, regardless.

@gucki

@jnicklas As you say, the elemenI itself is not visible - not only its content. So think it'd be more intuitive if page.find(element) would return nil if the element is not visible. But when page.find(element) returns an element, it should properly return its attributes and content as they are - no matter if it's visible or not.

@joliss
Collaborator
@foxycoder

+1 for this problem, and also:

<html>
<head>
  <title>Title Text</title>
</head>
<body>
  <h1>Some heading</h1>
  <p>Some paragraph</p>
</body>
</html>
page.has_xpath? "//title"
# => true

page.has_xpath? "//title", text: "Title Text"
# => false

page.should have_xpath "//title"
#=> true

page.should have_xpath "//title", text: "Title Text"
#=> Capybara::ExpectationNotMet Exception: expected to find xpath "//title" with text "Title Text." but there were no matches. Also found "", which matched the selector but not all filters.

Which indicates the title tag is found, but the text inside it remains invisible.

Although a page.title would be a nice shorthand to have, it will only provide support for that one tag. How about meta tags, canonical links, etc.? In my humble opinion, #find etc. can return nothing when content is invisible, but xpaths should return visible and invisible content, so it can be used as a more "advanced" fallback for niche situations..

I'd be happy to help code it, once there is agreed on what to do with this btw!

Thanks.

@agileapplications

Would be great to have a method that retrieves the text of an element independent of it's visibility. That method could also be used to retrieve the page title. So in my opinion no need for an extra page-title-method but wouldn't hurt of course.

If you keep the behaviour as earlier I would having #text returning always the text and e.g. #visible_text returning only text that is visible.

Best regards
Tobias

@murdoch

+1 can't query title text, but the following works ok:

<head><title>marflar</title></head>

should have_xpath("//title[contains(.,'marflar')]")

Is there anything wrong with just doing that?

@meredrica

The content of the title tag is definatively visible, so it should also be possible to check it.

@molfar

completely agree, the page title is the first thing checked in requests specs.

@john-patrik

I also ran into this roadblock today and stumbled upon this solution; http://stackoverflow.com/a/13755730

Seems to work out pretty well for me, so lots of kudos to the creator (not me)!

I'm copy/pasting the solution because I want to save you guys a click.


I had the same issues when I upgraded to Capybara 2.0, and managed to solve them by creating the following custom RSpec matcher using Capybara.string:

spec/support/utilities.rb

RSpec::Matchers::define :have_title do |text|
  match do |page|
    Capybara.string(page.body).has_selector?('title', text: text)
  end
end

Now, in a spec file where subject { page }, I can use:

it { should have_title("My Title") }
it { should_not have_title("My Title") }
@jsuwo

+1 for not being able to query <title> after upgrading to 2.0.

@LeahCim

Thanks @john-patrik. That's a great find and just what I needed. Something like this could be in Capybara by default as per @jnicklas's 2nd proposed solution above: #844 (comment)

@beaubrewer

Has there been an official decision or progress made here? Both solutions would work and I feel a solution (even if it proves to be less than perfect) is better than none at all. -I hate not being able to get through a tutorial :)

@jnicklas
Owner

Closed by #960

@jnicklas jnicklas closed this
@mhartl

I'd like to express my frustration about the breaking of backwards compatibility on the behavior of have_selector('title', text: ...). As others on this thread have noted, many people use have_selector because of its use in the Ruby on Rails Tutorial. As the author that tutorial, it is my job to keep it up to date, and the lack of support for the previous behavior of have_selector is both frustrating for me and (because the necessary fix adds extra code) increases the complexity for beginning users. It is my hope that future iterations of Capybara will take more care to preserve current (and, in this case, logical) behavior.

@jnicklas
Owner

@mhartl we've gone out of our way to make this more intuitive for Capybara 2.1. We've added more control over whether to return only visible text or all text and we've added a new api for querying and asserting on the page title.

I'm sorry that behaviour broke this significantly for you, we didn't anticipate that this change would affect users. We had a lengthy beta period for Capybara 2.0, and this issue was not reported during that time. If it had, we could have done something about it sooner.

However, I don't agree that the way you are suggesting to query for the page title is a good idea. Once Capybara 2.1 is released, you should update the tutorial to suggest the use of have_title, which will be the official API from now on. have_selector("title", text: "something") might work or it might not, it depends on the driver. Imo, it's a perfectly valid interpretation for the title element not to have any text.

@jnicklas
Owner

@mhartl it just occured to me that since Capybara 2.1 will default Capybara.ignore_hidden_elements to true, and the title element is invisible, it won't be possible to query its content this way, visible text or not.

@mhartl

@jnicklas I actually never liked using have_selector in this context; in the original Rails 2.3 version of the book I used Webrat's have_tag, but that disappeared when Webrat was deprecated. Using have_selector was the closest, if imperfect, substitute. I agree that have_title is much better, and I'll plan to update the tutorial accordingly. Thanks!

@topfunky

For reference, Capybara now has have_title in 2.1.0.beta1 as described here:

http://www.elabs.se/blog/60-introducing-capybara-2-1#asserting_against_the_page_title

@mhartl

Awesome. I can confirm that this works with the Rails Tutorial sample app and have updated the beta book accordingly.

I noticed that the selenium-webdriver gem dependency didn't install automatically. Is that intentional? To get it to work, I had to add it explicitly to my Gemfile:

group :test do
  gem 'selenium-webdriver', '2.0'
  gem 'capybara', '2.1.0.beta1'
end
@topfunky

@mhartl See issue #1018 for the Selenium fix.

@husnainAshraf

This work for me

get :home
assert_select "title", "Ruby on Rails Tutorial Sample App | Home"

can see http://stackoverflow.com/questions/3971449/verifying-page-title-with-rspec

@andresprogrammer

I have one afternoon and half of the morning only with the title problem. Finally I found a solution and I just sign up to write it:

expect(page.title).to   eq("My title")

Good luck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.