angular / protractor

E2E test framework for Angular apps
http://www.protractortest.org
MIT License
8.75k stars 2.31k forks source link

Future Feature Idea: Advanced $('') Selection Syntax #397

Open juliemr opened 10 years ago

juliemr commented 10 years ago

We should extend the traditional css selector syntax to be able to understand things like angular bindings. This would be a cleaner way to do all element selection.

Note that this is probably a post 1.0 feature.

tbosch commented 10 years ago

+1

SimplGy commented 10 years ago

It might be a bit misleading to use $() to return an object that doesn't match jQuery, Zepto, or jQuery Lite syntax at all. Confused the heck out of me. Is it a good idea to try to match the subset of the api that is exposed to what jQuery has (getText() -> text() and so on)?

If it's better thought of as a different type of object, it really seems to make sense to query it with a unique syntax. element By.css is unique, but pretty gnarly :)

wlingke commented 10 years ago

@SimpleAsCouldBe It was a bit misleading to me at first too, but I think we just need better documentation that teaches new users how to think about protractor tests. It's not like a unit test where you're testing from a developer's POV. This is testing from a user's POV so it doesn't even make sense that jQuery would be used inside a protractor test (maybe except for rare scenarios).

juliemr commented 10 years ago

@SimpleAsCouldBe I was also worried about the confusion but everyone kept asking for it :)

I should clarify the idea behind this post - it's to extend the css selectors that $ uses so that you could find angular bindings and models. It might look something like this:

$('div.myClass {{myBinding}}'); // Finds the element with binding myBinding inside a div with class myClass
SimplGy commented 10 years ago

Maybe I should move this discussion elsewhere? This is the API people interact with most of the time while using protractor, so it's worth considering carefully, I think.

I think folks kept asking for it because they love jquery syntax. Seems reasonable that concept extends to the selectors as well as the returned object.

I think what everyone really wants is an object that mirrors the jquery syntax where appropriate for this context. Just in my small test, where I check a login form with both a good and bad password, I've had to re-learn new syntax for the equivalents of:

$('.alert')       // element(By.css('.alert'))
.text()           // .getText()
.val()            // .getAttribute('value')
.is(':visible')   // .isDisplayed()

Why ask folks to learn a new syntax for tasks they're used to doing already? (not rhetorical, I want to understand protractor) Is it because having element By.css return a jquery-like object is arguably good but having element By.model return something different and more related to ng-model and data bindings is a better fit?

I understand that in some cases, as @wlingke points out, a different thought pattern is needed to better match the idea of user interaction. myInput.sendKeys() is a good example of this. On the other hand, many of the tasks of DOM selection and inspection that I need to do to make assertions are already easy to do in jQuery, and there's a huge pool of developers out there who know how to do it. Why not extend a jqLite object with the extra protractor mixins?

I think the idea of extending selector syntax is a good one--to use strings to represent meaning instead of having 3 or more selection mechanisms. This just gets it closer to a style folks are used to. Any move towards mirroring familiar syntax seems like a good decision for API usability. Maybe stuff like this:

element('{{myBinding}}') // An array of elements that are bound to this model
element('.alert{{errorMessage}}') // the element with a class of .alert and bound to errorMessage
element('[ng-model=credentials.user]') // the element(s) attached to this model (not sure making up a char to find a model is good idea, but the attribute syntax is a near fit)
juliemr commented 10 years ago

@SimpleAsCouldBe A couple points:

1) element(by.css()) and element(by.model()) return the same type of object now (just making sure there's no confusion here.) $(foo) === element(by.css(foo)).

2) I think the question about jquery syntax is legitimate and interesting, and I'm glad people are discussing it. I definitely want to hear more opinions! Here's my thoughts so far - it's appropriate to use familiar jQuery syntax where it's helpful and does what's expected, but when things are subtly different from how they'd work in jQuery or jQLite I'd like to avoid making them look the same and adding confusion. I think there's also value for those coming from webdriverJS (or webdriver Java for that matter) to have things that wrap webdriverJS look like their original form. I think we're on the same theoretical page here. I think that the argument for having aliases for getText -> text and getAttribute('value') -> val is reasonable - I've been hesitant though because I worry it's more confusing to have an object that half meets expectations. You're going to have to know the element API anyway.

SimplGy commented 10 years ago
  1. Makes sense. I was trying to guess at a reason for needing a significantly different object type. Maybe it's interesting to consider element(by.model()) returning something data-oriented and element(by.css()) returning something dom oriented. Probably better to provide the richest object possible to the end user though instead.
  2. Cool. I'd expect to have to learn new methods and return shapes for behaviors that are new. I wonder if there is some low hanging fruit--a set of common methods in jQuery that could reach API compatibility without too much effort. (I'm not sure how different the webdriver object is, it looks dramatically so). Some documentation on the returned element would go a long way. I can help there at least. I couldn't get the debugger mode to work, so inspecting the objects meant serializing them to the node console one at a time, it hurt and I want to prevent others that pain.

I wonder if it's not too hard to reach compatibility with relevant portions of jqlite. I just took a close look at it. Seems like you could break the jQuery API into these categories

I think being opinionated and up-front about the recommended way to use this could be a strong approach here. I could see the documents saying something like:

Use familiar jquery syntax for traversing the DOM and reading values out. We support the below subset of the jquery API. For dom input and manipulation, you should interact with the elements as though you were an end user, based on the following API.

A set of jqlite that provides traversal and reading looks like this:

// Reduced from http://docs.angularjs.org/api/angular.element:

// ----------------------------------- Useful Set for e2e testing
// DOM Traversal (Useful for finding the thing to act on or read a value from)
.eq()
.find()   // Limited to lookups by tag name
.next()   // Does not support selectors
.parent() // Does not support selectors

// Reading Values (Useful for making assertions)
.attr() // read only
.css()  // read only
.data() // read only
.text() // read only
.val()  // read only
.prop() // read only
.html() // read only. Useful like jasmine-jquery's .toContainHtml() method.
.contents()
.hasClass()

// ----------------------------------- Counterproductive for e2e testing
// DOM Manipulation (not needed for e2e testing)
/*
addClass()
after()
append()
clone()
empty()
prepend()
removeAttr()
removeClass()
removeData()
replaceWith()
remove()
wrap()
toggleClass()
*/

// Event Editing (not needed for e2e testing)
/*
bind()
on()
off()
one()
children()
unbind()
triggerHandler()
*/

// ready() Not needed, timing related
hankduan commented 9 years ago

@juliemr is this still something that we want to do? Now that it's possible to chain multiple element.all, it's pretty straightforward to implement this -- all that's needed is inside Protractor parse the selector in $(selector) and expand it out to element/element.all form. The only thing is to come up with a list of symbols to represent the ng elements (i.e. {{binding}}, @model, %repeater). It really comes down to memorizing a bunch of symbols to represent the elements (i.e. . and # for css). Do you think it's something that will be useful though?

guiprav commented 9 years ago

@hankduan Maybe this would be more verbose, but I'm still more inclined to cheer for pseudo-element syntax, like jQuery does (e.g. :contains(...)). :repeater("item in items"), :binding("etc") would work really well IMO.

Besides looking good (IMO), they have an additional benefit: the syntax for combining them is already well-defined, but {{binding}}, for example, isn't. How would we select an element with a class and a specific binding? .class{{binding}} looks a little strange to me (we might need that syntax when a binding is used as or interpolated with class names or attribute values, though)...

longlho commented 9 years ago

Are we still interested in some DOM traversal sugar methods? I'm happy to submit a PR to throw some in (parent, children, next, prev...)

jlin412 commented 9 years ago

+1

On Mon, Jun 1, 2015 at 12:10 PM, Long Ho notifications@github.com wrote:

Are we still interested in some DOM traversal sugar methods? I'm happy to submit a PR to throw some in (parent, children, next, prev...)

— Reply to this email directly or view it on GitHub https://github.com/angular/protractor/issues/397#issuecomment-107612827.

codef0rmer commented 8 years ago

I faced the similar problem for my projects too, so I decided to solve it differently. Today releasing Proquery: https://github.com/codef0rmer/proquery

Do check and provide feedback.

melbourne2991 commented 8 years ago

Has any work been done on this, may submit a PR if it's still in scope and it means I can avoid falling back on xpaths for advanced selection

codef0rmer commented 8 years ago

Proquery v2.0.0 released with more stability with EC and Iframes. You can now use $ instead of $p. Check it out here: https://github.com/codef0rmer/proquery

P.S. My apology if I'm spamming anyway.