My application under test has been developed by external suppliers so I have no control over the HTML structure. The application is extremely Javascript and Ajax heavy, with numerous dynamically generated buttons and auto-complete lists.
In other words, the characteristics of the pages are that they are filled with:
Elements with no fixed IDs (IDs are generated on the fly and have
numbers or other text dynamically added to them)
The same happens with some classes
Most of the times the buttons have no text associated with them since they are either custom coded 'down' arrows for lookup lists
(which aren't lookup lists but hidden divs) or '+' and '-' icons to
maximise or minimise portions of the content. -
It is therefore very difficult to identify these elements, especially the buttons.
I am trying to write a generic 'I click on the button near y' type of step so that it is not necessary to hardcode each and every button (assuming I can even get something to identify them with) into each and every test.
The thinking behind this is that normally there is a label of some sort close to the button at least.
What I want to to is to find the text label, then see if there is a button inside the same scope, and if there is not, move 'back' through the parent elements, and check if there is a button inside the scope of each parent level, up to 5 parents.
There might be all sorts of problems with this approach but I am just curious to see if this will work in general. I have run into some problems.
First I tried to use Xpaths, so I got the Xpath of the parent through :
$parentelement = $element->getParent();
$parentXpath->getXpath();
This would give me an Xpath of : (//html//span[text()='Cost center'])[1] and moving up through the parent elements all the time, they would become successively:
(//html//span[text()='Cost center'])[1]/..[1]
(//html//span[text()='Cost center'])[1]/..[1]/..[1]
and so forth.
The actual button is located in: (//html//span[text()='Cost center'])[1]/..[1]/..[1]//button but it has to go through all the parent elements in order to get there, so it will start with (//html//span[text()='Cost center'])[1]//button and should end with (//html//span[text()='Cost center'])[1]/..[1]/..[1]//button where it should find the button.
Trying to use Xpath I used:
$button_element = $session->getPage()->find('xpath',$parentXpath."//button")
I soon saw that the 'find' command appends an //html to the front of your xpath string so the Xpath that it tried to use ended up being (for each parent Xpath, but using this one as an example):
(//html(//html//span[text()='Cost center'])[1]/..[1])
I then stripped out the brackets as well as the //html, leaving me with:
//span[text()='Cost center'][1]/..[1]
but when I tried:
$button_element = $session->getPage()->find('xpath',$strippedParentXpath."//button")
I got the following error:
SyntaxError: Failed to execute 'evaluate' on 'Document': The string '(//html//span[text()='Cost center'][1]/..[1]//button)[1]' is not a valid XPath expression
However, Firepath can execute this expression and does not show a syntax error for it, although it does not find the actual button (since the button is actually located one level up, where Firepath DOES find it).
So my question 1 is: What is wrong with my Xpath that I can't use it in the find? It actually looks as if //span[text()='Cost center'][1]//button does not throw the same exception, since as I said, I am looping through the parent Xpaths, and it starts with //span[text()='Cost center'][1]//button. It crashes on //span[text()='Cost center'][1]/..[1]//button.
My second option was to get the parent element each time, starting with finding the text on the page, but then to search for a button inside the scope of the parent element using the findbutton functionality.
Looping through the parent elements (up to a maximum of 5):
$parentelement = $parentelement->getParent();
$butonelement = $parentelement->findbutton('xxx');
In other words, find ANY button in the scope of the parent element. The problem I have is how to specify a generic 'button'.
One has to associate SOME text with the button (depicted by the 'xxx' above).
But this is a typical example of buttons in the application:
<button class="autocomplete_button" type="button" id="button_OM_1"> </button>
Where the class is used more than once, and the ID is auto-generated and not the same number all the time. There is no text associated with the button since the class specifies an image.
Question 2: So how can I use 'findbutton' to generically find a 'button' no specific distinguishing characteristics? Please note that I actually did try findbutton("button"), taking the chance that there might be a 'button' somewhere in a button, but this did not work either. At least, it doesn't work consistently and by that I mean that the same test randomly seems to either find or not find the same button when I run the test a couple of times.
After doing some more investigation on this issue I have found the following:
My method of trying to find the closest button to a piece of text via traversing 'up' through the scope of the divs and spans around the text (using xpath) is actually working.
What is NOT working is SAHI, which I am using as the web driver. In other words, it is not a Behat/Mink problem, it is SAHI specific issue.
I tried the same code using Selenium2 and it executes perfectly.
I still require an answer to question 2 - how can I use findbutton() without a specific parameter such as the ID, name or value but I will see if I can find an answer to that question separately and on the Behat user group since I do think that is a Behat/Mink specific issue.
I normaly use css selector and with that, I use to navigate to the class and ID's that the button is inside. it is easier than xpath I think, like you can use
$this->getSession ()->getPage ()->find ( 'css', '.parrent1 .parrent2 .autocomplete_button ' );
I think this will help you as you know which button your gonna use in each scenario
Related
I have a set of tabs with proper roles and attributes for accessibility support. The content that tab controls gets loaded in via ajax. But each wrapper for the content loaded in also has proper tab pane roles and attributes.
The problem is, when I run an automated audit using Chrome Accessibility Tools, the test fails stating that the corresponding ID of the tab pane is missing for all of the tabs except the one that's currently active (because that wrapper with ID has been loaded). The exact error states: "ARIA attributes which refer to other elements by ID should refer to elements which exist in the DOM."
Since the ID will exist once the tab with the corresponding aria-controls attribute is active, is this really an error? Or is this just a case of a false positive because it's an automated test and they can only do so much.
In summary, What does aria-controls do and does it really need to refer to an ID that currently exists in the DOM?
aria-controls give your assisting technology a way to move to the controlled element.
If this element is not in the DOM or can't be accessed, then yes it's an error.
The two (the element with aria-controls as well the element with the referenced id) must exist at the same time, whether at page render or via JS injection.
The DOM is parsed by the UA/AT combo before the user even gets to the control or your script fires to make it exist. If you use JS injection then you need to make sure the DOM is re-parsed.
This would apply to aria-owns as well.
I don't know whether the following would work in your architecture, but it would solve the error problem:
Design the tabs so they are all in the page at the time it loads. Format those that should not be shown to be outside the viewport using absolute positioning and something like "left: -99em." Use AJAX to reset the positioning when the time has come to display the tabs. The result is that the ARIA ID dependencies will always be valid because the tabs are always part of the DOM.
I'm trying to click on "Mr" from the drop down list I've tried a combination of things but non of them seem to work.
I've even tried xpath which is usually reliable but for this case its failing.
$browser.element(:xpath, "/html/body/div[1]/div[1]/div[1]/div/div[2]/div[1]/div[2]/div/div[2]/div/div[2]/div[2]/form/div[2]/div/div[2]/div/div/div/div/div/ul/li[2]/a").click
The XPath suggested by Saurabh Gaur, can be written in a more readable Watir-like fashion using:
$browser.ul(class: 'dropdown-menu').link(text: 'Mr').click
Note that this assumes that there is only one ul element with class dropdown-menu. If there are multiple, you will need to scope the search to the specific dropdown using an element that likely exists higher in the DOM.
However, given there is likely only one link with text "Mr", you can probably get away with simply:
$browser.link(text: 'Mr').click
Given the link is a dialog that switches from hidden to visible, you may need to also wait:
$browser.link(text: 'Mr').when_present.click
Your xPath is positional which depends on element position.. it will not work if elements are change their position means adding some elements after some action on the page.
After seeing your attached image I have generated following xPath as below :-
//ul[contains(#class, 'dropdown-menu')]/descendant::span[contains(.,'Mr')]/parent::a
Try with this xPath.. May be it will work...:)
I'm using Capybara on a form that has multiple checkbox fields with an "Other" option. The Capybara API gives us
page.check('Other')
but no way (that I can find) to limit the scope to a given field. You can limit the scope based on a CSS (or XPath) selector, but since none exist that make sense this requires that I either change the (ugly legacy) markup of the page just to accommodate Capybara, which seems like the wrong solution. (In a perfect world I'd have time to completely refactor the markup, and wind up with something semantically sensible that also gave me a way of selecting a scope for Capybara, but this is not a perfect world, and I don't want to just jam in classes all of the place to accommodate Capybara.)
This
page.within('[name=FieldName]') do
page.check('Other')
end
doesn't work, either, since Capybara is looking for a single parent node that it can use as the scope, and this gives a set of checkboxes. (It would be nice if Capybara supported that, but it doesn't.) It's like I'm passing a deck of cards to search through, and Capybara wants the box the cards go in, but I don't have any box.
I'd like to be able to do something like this
page.check('Other', :in => 'FieldName')
but I can't find anyway of doing that. As far as I can tell, the only options that can be passed in are text, visible, and exact. Am I missing something? Is there a way to do this without resorting to ugly workarounds?
Since you have a css-selector that can find the checkbox, you can use the find method to locate the checkbox.
page.find(:css, '[name=FieldName][value=Other]')
Then to check the checkbox, use set (which is used by the check method):
page.find(:css, '[name=FieldName][value=Other]').set(true)
You could also use the click method:
page.find(:css, '[name=FieldName][value=Other]').click
This is not the most elegant solution, but since no one is posting a better one (so far), here's the best I've come up with.
Just execute a script to do what you need. In my case I'm using jQuery:
page.execute_script('$("[name=FieldName][value=Other]").trigger("click");')
I want to write a function that identifies all the links on a particular HTML page. My idea was to use XPath, by using a path such as //body//a[x] and incrementing x to go through the first, second, third link on the page.
Whilst trying this out in Chrome, I load up the page http://exoplanet.eu/ and in the Chrome Developer Tools JS console, I call $x("//body//a[1]"). I expect the very first link on the page, but this returns a list of multiple anchor elements. Calling $x("//body//a[2]") returns two anchor elements. Calling $x("//body//a[3]") returns nothing.
I was hoping that incrementing the [x] each time would give me each unique link one by one on the page, but they seem to be grouped. How can I rewrite this path so that I picks each anchor tag, one by one?
Your //body//a[1] should be (//body//a)[1] if you want to select the first link on the page. The former expression selects any element that is the first child of its parent element.
But it seems a very odd thing to do anyway. Why do you need the links one by one? Just select all of them, as a node-list or node-set, using //body//a, and then iterate over the set.
If you use the path //body/descendant::a[1], //body/descendant::a[2] and so on you can select all descendant a elements of the body element. Or with your attempt you need braces e.g. (//body//a)[1], (//body//a)[2] and so on.
Note however that inside the browser with Javascript there is a document.links collection in the object model so no XPath needed to access the links.
Does anyone know how to delete an element from the source using Watir? There doesn't seem to be a method for removing elements. Perhaps I'm missing something.
If you know JavaScript, you could execute any JavaScript code on the page.
Example:
browser.execute_script("some javascript code")
I am not a JavaScript ninja, but this question could help you: JavaScript: remove element by id.
Remove elements by css:
browser.execute_script("[...document.querySelectorAll('.some.class')].map(e => {e.parentNode.removeChild(e)})")
We can remove it with javascript. Here's an example to remove a breadcrumbs div element but it's id:
browser.execute_script("bd = document.getElementById('breadcrumbs'); bd.parentNode.removeChild(bd);")
The Purpose for Watir is to do web testing, which is to say drive the browser as if a user was interacting with it. That means doing the things a user could do, clicking on stuff, filling in input fields, etc. It also means being able to verify what is there on the screen that the user can see or interact with.
Since a user cannot delete elements, there is no means by which to do that using the tool.
If the application provides a way for users to 'remove' or 'delete' something, like closing a simulated window, removing a tab etc, then you need to do that by simulating what the user would do (usually clicking on some specific element) in order for that to happen.