I am trying to use Selenium to click on a ::after pseudo element. I realize that this cannot be done through the WebDriver directly, but cannot seem to figure out a way to do so with Javascript.
Here is what the DOM looks like:
<em class="x-btn-split" unselectable="on" id="ext-gen161">
<button type="button" id="ext-gen33" class=" x-btn-text">
<div class="mruIcon"></div>
<span>Accounts</span>
</button>
::after
</em>
This is what the above element looks like. The Left hand side of the object is the 'button' element and the :after element is the right hand side with the arrow which would bring down a dropdown menu when clicked. As you can see that the right hand side has no identifiers whatsoever and that is partially what is making this difficult to do.
I have seen these two links in stackoverflow and have attempted to combine the answers to form my solution, but to no avail.
Clicking an element in Selenium WebDriver using JavaScript
Locating pseudo element in Selenium WebDriver using JavaScript
Here is one my attempts:
string script = "return window.getComputedStyle(document.querySelector('#ext-gen33'),':before')";
IJavaScriptExecutor js = (IJavaScriptExecutor) Session.Driver;
js.ExecuteScript("arguments[0].click(); ", script);
In which I get this error:
System.InvalidOperationException: 'unknown error: arguments[0].click is not a function
(Session info: chrome=59.0.3071.115)
(Driver info: chromedriver=2.30.477700 (0057494ad8732195794a7b32078424f92a5fce41),platform=Windows NT 6.1.7601 SP1 x86_64)'
I've also tried using the Actions class in Selenium to move the mouse in reference to the left hand side, similar to this answer as well. I think it may be because I don't know what the offset is measured in and the documentation doesn't seem to give any indication. I think it is in pixels??
Actions build = new Actions(Session.Driver);
build.MoveToElement(FindElement(By.Id("ext-gen33"))).MoveByOffset(235, 15).Click().Build().Perform();
This attempt seems to click somewhere as it gives no errors, but I'm not really sure where.
I'm attempting to automate Salesforce (Service Cloud) in c# if that helps.
Maybe someone can offer a solution?
I've encounter the same problem while writing Selenium tests for Salesforce and managed to solve it by direct control over mouse using Actions.
Wrapper table for this button has hardcoded width of 250px, and you have spotted that. To locate where the mouse is, you can use contextClick() method instead of Click(). It simulates right mouse button so it will always open browser menu.
If you do:
Actions build = new Actions(Session.Driver);
build.MoveToElement(FindElement(By.Id("ext-gen33"))).ContextClick().Build().Perform();
you will spot that mouse moves to the middle of the WebElement, not the top left corner (I thought that it does too). Since that element width is constant, we can move mouse just by 250 / 2 - 1 to the right and it will work :)
code:
Actions build = new Actions(Session.Driver);
build.MoveToElement(FindElement(By.Id("ext-gen33"))).MoveByOffset(124, 0).Click().Build().Perform();
For those who are trying to do this in Python, the solution is below:
elem= driver.<INSERT THE PATH TO ELEMENT HERE>
ActionChains(driver).move_to_element_with_offset(elem,249,1).click().perform()
Basically here I'm finding my element in the DOM and assigning to a WebElement. The WebElement is then passed the method move_to_element_with_offset as a param.
I got the px values for the element from developer tools.
PS: use this import- from selenium.webdriver.common.action_chains import ActionChains
You can read more about Action chain class and its method move_to_element_with_offset here: http://selenium-python.readthedocs.io/api.html.
Hope this helps.
Maciej'a answer above worked with WebDriver, but not with the RemoteWebDriver (Selenium 3.12.0) against Firefox V.56. We needed a solution that worked for both local and remote. Ended up using keyboard shortcuts to invoke the Navigation Menu drop down. As an added benefit, this also removes the need to use offsets.
String navigationMenuDropdownShortcutKeys = Keys.chord(Keys.ESCAPE, "v");
new Actions(driver)
.sendKeys(navigationMenuDropdownShortcutKeys)
.perform();
Im going to provide an alternative that may work for some scenarios, at least it did the trick for me, and is relatively easy to implement in any language using selenium via a JS script.
In my scenario there was an ::after pseudoelement containing the functionality of a button. This button was contained in a position relative to another element under it.
So I did the following:
Get the element that I can, in this question scenario would be that span.
Get the coordinates of the element.
Calculate the coordinates realtive to that element of the pseudoelement you want to click.
Click on those coordinates.
This is my code using perl, but I'm sure you can do the same in any language:
my $script="
function click_function(x, y)
{
console.log('Clicking: ' + x + ' ' + y);
var ev = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': true,
'screenX': x,
'screenY': y
});
var el = document.elementFromPoint(x, y);
el.dispatchEvent(ev);
}
var element = document.getElementById('here_put_your_id'); //replace elementId with your element's Id.
var rect = element.getBoundingClientRect();
var elementLeft,elementTop; //x and y
var scrollTop = document.documentElement.scrollTop?
document.documentElement.scrollTop:document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft?
document.documentElement.scrollLeft:document.body.scrollLeft;
elementTop = rect.top+scrollTop;
elementLeft = rect.left+scrollLeft;
console.log('Coordiantes: ' + elementLeft + ' ' + elementTop)
click_function(elementLeft*1.88, elementTop*1.045) // here put yor relative coordiantes
";
$driver->execute_script($script);
After going through numerous article and the blogs I figured out the way to determine how to detect the Pseudo element in the DOM in the Selenium. And validate based on the certain conditions if it is present or no.
Step 1
Find the path to the parent element which consist the pseudo element and pass under the findElement as shown below
WebElement pseudoEle = driver.findElement(path);
Step 2
String display = ((JavascriptExecutor)getWebDriver()).executeScript("return window.getComputedStyle(arguments[0], ':after').getPropertyValue('display');",pseudoEle).toString();
In the above line of code pass the desired Pseudo code in the place of ":after" (In my case I was looking for 'after') and the property value which is changing based on the pseudo code is present or no (In my case it was 'display').
Note: When the pseudo element was present javascript code return 'Block' which in turn I saved in the display field. And use it according to the scenario.
Steps to determine the right property value for your case
Inspect the element.
Navigate to the parent element of the pseudo code.
Under the Styles tab figure out the field(Green in color) whose value change when the pseudo code is present and when not present.
I am sure this would help you to the great extent. Kindly like and support, would encourage me to post more solutions as such.
Thanks!
Related
I want to take the logo of google.com and rotate it, not big deal and nothing really important, I just wanna learn JS in a fun way.
When I use the select element tool (Ctrl+Shift+c in chrome) I get that logo's id is "logo", so I'm trying this way:
const logo = document.getElementById("logo");
But I get this everytime I try:
undefined
I'd appreciate any help, TY <3
You need to target it's class, id or an attribute. Assuming you're talking about Google's default search homepage, the class seems to be using a dynamic value (you can still target using that value but your code will not work if you try to run it again when the values have changed) so you could target it's alt attribute instead and use the transform rotate() css property on it like this:
const logo = document.querySelector('img[alt="Google"]');
logo.style.transform = "rotate(180deg)";
The above two lines should rotate the Google logo if you run it in the browser console.
I'm assuming you entered that in the JS console.
The result of the const logo = ... statement is undefined, but that doesn't mean the const didn't get assigned (though in case the element doesn't exist, then logo did get assigned undefined...).
If the element does exist and you follow up with logo.style.transform = 'rotate(90deg)', it should work out fine.
As an aside, document.querySelector("img[alt=Google]") may be more bullet-proof for Google's front page.
See:
I am trying automation test on following website - http://www.arzoo.com
when we search flight,
I am unable to click select on particular flight.
I used Xpath but it doesn't get the element if it's at bottom or middle of the page so then I need to use:
JavascriptExecutor jsx2 = (JavascriptExecutor)driver;
jsx2.executeScript("window.scrollBy(0,750)", "");
driver.findElements(By.xpath("//a[text()='Select']")).get(15).click();
but I don't want to use scroll to position. different screens will need different sizes.
I planned to use css sector but still no success.
Try the following xpath:
driver.findElement(By.xpath("//a[contains(#class, 'btn-primary')]")).click();
if not, try
driver.findElement(By.xpath("//li[contains(#id, 'result_0')]/div/div/div/div[2]/a")).click();
See the following for reference:
Get Nth child of a node using xpath
Xpath changing after the page gest loaded every time
Wrap the below code inside executeScript()
let allSelectButtons = document.querySelectorAll(".booking-item-flight-details .booking-item-arrival a");
for(i=0;i<allSelectButtons.length;i++) {
allSelectButtons[i].click();
}
According to a discussion at Github one cannot position a standard dialog (api), but panel dialogs (api) can be positioned.
A simplified demo shows that this is true:
var position = this._mdPanel.newPanelPosition().bottom(0).right(0);
The Angular Material docs show a method that allows positioning relative to the clicked element (or whatever is passed in). I'm unable to get this to work, however.
var target = el.target;
var position = this._mdPanel.newPanelPosition().relativeTo(target);
Passing in hard values for .top() and .right(), for example, allows positioning relative to the viewport. I can't get positioning relative to the clicked element, though. How is this supposed to work?
I've been working with Angular Material for the past several months and still find the documentation lacking, so forgive the length of this post as my pseudo documentation on the issue. But here is what I do know:
I've only been able to get the panel location to work, relative to a target element, by chaining the addPanelPosition function onto the relativeTo function as such:
var position = this._mdPanel
.newPanelPosition()
.relativeTo(ev.target)
.addPanelPosition('align-start', 'below') // or other values
(in this case, ev is the $event object passed by ng-click)
I was able to track down the acceptable parameters for addPanelPosition and they are the following:
Panel y position only accepts the following values:
center | align-tops | align-bottoms | above | below
Panel x Position only accepts the following values:
center | align-start | align-end | offset-start | offset-end
Interstingly enough, in the Angular Material demo, they use the this._mdPanel.xPosition.ALIGN_START and this._mdPanel.yPosition.BELOW properties which simply resolve to strings as their x and y values for the addPanelPosition function. I've always gone straight with the string values. However, using string values could be problematic if the development of this feature is still in flux and they change the acceptable string values.
I'll point out one more issue I've seen.
Another trick they use in the demo is to specify a class name in the relativeTo function instead of a target element, then place that class on the target element itself. The reason this approach can be helpful is because the $event object from ng-click can provide different target elements based on what exactly was clicked. For example, clicking the button <div> is going to give a different target than clicking the <span> text inside the button. This wil cause your panel to shift locations unless you provide the additional functionality not to do so.
Codepen
I took their demo and really cut it down to size to focus on this issue. You can see the updated codepen here
As I post in a comment, here you can see it working on a plunker.
My solution is very close the to #I think I can code answer. However, in my answer, instead of a menu, a <md-dialog> is displayed when the button is clicked, as it's requested in the OP.
Besides the working plunker with a dialog, there is no much to add to the good #I think I can code answer. As it's shown in the angular-material md-panel demo, the key here is to set the position of the panel relative to the button. To do that (like in the angular-material demo), we can use a specific css class (demo-dialog-open-button in my example) to find the target element. this is a tricky thing in my opinion...but it works well for this use case (it's also well explained in the other answer).
Code for reference, see the plunker for the complete details:
html (note the css class added to the button):
<md-button class="md-primary md-raised demo-dialog-open-button" ng-click="ctrl.showDialog($event)">
Dialog
</md-button>
JS controller.
var position = this._mdPanel.newPanelPosition()
.relativeTo('.demo-dialog-open-button')
.addPanelPosition(this._mdPanel.xPosition.ALIGN_START, this._mdPanel.yPosition.BELOW);
Hope it helps
Dialogs are very simple widgets. Trapping focus is about the most complicated thing they do. It pains me that your issue has evolved into such a complex one.
Just to state the obvious, you do have complete control over positioning any individual dialog thanks to your configured class name.
.demo-dialog-example {
top: 20px;
left: 20px;
}
Also, in your showDialog method, why not set up a call-back via a promise for the open method? Something like:
this._mdPanel.open(config).then(function() {
var dialog = angular.element(document.querySelector('.demo-dialog-example'));
//Centering, positioning relative to target, or draggable logic goes here
});
I respect that you are trying to improve the logic of the plugin and do things the "Angular way", but these relatively simple requirements should not be causing you this much heartache.
Warning: not duplicate with existing questions, read through
I know I can have an event listen on changes on an contenteditable element.
What I would like is to be able to know what the changes are.
For example:
inserted "This is a sentence." at position X.
deleted from position X to Y.
formatted from X to Y with <strong>
Is that possible? (other than by doing a diff I mean)
The reason for this is to make a WYSIWYG editor of other languages than HTML, for example Markdown.
So I'd like to apply the changes to the Markdown source (instead of having to go from HTML to Markdown).
You may be able to do something with MutationObservers (falling back to DOM Mutation events in older browsers, although IE <= 8 supports neither) but I suspect it will still be hard work to achieve what you want.
Here's a simple example using MutationObservers:
http://jsfiddle.net/timdown/4n2Gz/
Sorry, but there is no way to find out what the changes are without doing a diff between the original content and the modified one when changes occur.
Are you looking for this
var strong=document.createElement("strong");
var range=window.getSelection().toString().getRangeAt(0);
range.surroundContents(strong);
this was for third part
You just need to select what you want to surround using real User interaction.
If you wanna do it dynamically
var range=document.createRange();
range.setStart(parentNode[textNode],index to start[X])
range.setEnd(parentNode[textNode],index to end[Y])
range.surroundContents(strong);
For 2nd Part
range.deleteContents()
1st part can be done by using simple iteration
var textnode=// node of the Element you are working with
textnode.splitText(offset)
offset- position about which text node splitting takes place[here==X]
Two child Nodes have been created of the parent editable Element
Now use simple insertBefore() on parent editable Element Node.
hope you will find it useful
The API you're looking for does not exist, as DOM nodes do not store their previous states.
The data / events you're wishing to get back are not native implementations in any browser Ive come across, and I struggle to think of a datatype that would be able to generically handle all those cases. perhaps something like this:
function getChanges() {
/* do stuff here to analyse changes */
var change = {
changeType : 'contentAdded',
changeStart : 50, /* beginning character */
changeContent : 'This is a sentence'
}
return change;
}
Since you're trying to get custom events / data, you're probably going to need a custom module or micro-library. Either way, to look at the changes of something, you need somehow be aware of what has changed, which can only be done by comparing what it was to what it is now.
I am working on Selenium WebDriver.
I need to point the mouse to an element and perform click on it and I want to use javascript here instead of Xpaths.
The javascript of that element is not a method so that I can just fire it directly.
I am confused how to create a javascript so that the method when auto-executed should go to that object (I want to point to that object using its javascript only) and perform click.
Element's javascript:
javascript:setParam(paramOrderNbr, '4');
go('survey_editing.jsp','actMoveItemUp);
Please help!
Kumar
try this:
String cssSelector =.... //css selector of the element you want click on
JavascriptExecutor js = (JavascriptExecutor) driver;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("var x = $(\'"+cssSelector+"\');");
stringBuilder.append("x.click();");
js.executeScript(stringBuilder.toString());
hope this works for you
Good job.
But try to modify a lil bit your css selector.
Try simply map[name="edit_1"]> area
But before you try to execute anuthing verify with firebug ( i use firepath, firebug addon in ffox) to verify that your css selector is correct.
Then try execute the code I mentioned above. It always works.
But also is possible to try another approach. If your selenium test is connected with pointing out web element with onmousehover action handling.
Then is possible to user action builder:
WebElement mnuElement;
WebElement submnuElement;
mnEle = driver.findElement(By.Id("mnEle")).click();
sbEle = driver.findElement(By.Id("sbEle")).click();
Actions builder = new Actions(driver);
// Move cursor to the Main Menu Element
builder.moveToElement(mnEle).perform();
// Giving 5 Secs for submenu to be displayed
Thread.sleep(5000L);
// Clicking on the Hidden SubMenu
driver.findElement(By.Id("sbEle")).click();
please inform as soon as you check this one.
I've made a little investigation on your problem. And now I'ma a lil bit frustrated.
Firebug is unable to locate anything which is contained in <script> tags.
See the picture below
So if we are unable of locating element using standard tree DOM model then the last assumption is left (in my opinion). I'll share only the idea I would implement if come across with your problem. Simply try to click on fixed coordinates using js.But this is considered to be bad approach. It is explained here
So returning back to the js locating coordinates to click you can use this
Using described part we locate x, y coordinates of the element we need to locate. And using this
you can actually perform the click.
Something like that:
JavascriptExecutor js = (JavascriptExecutor) driver;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("x.trigger("click", [x, y]);"); //where [x,y] you've already //obtained
js.executeScript(stringBuilder.toString());
By the way, you can get to know about advanced user actions here . I find it quite helpful in some cases.
But it still seems to me that somehow it is possbile to locate your needed element in DOM.
Hope my answer helps somehow)