I am using selenium with python for the website automation,and that includes navigation and file downloads.If the page is idle for around 15 mins(roughly,not exact),confirmation pop up appears warning the session timeout and it asks to click "OK" to continue.
I know I can use the following code to deal with the confirmation pop up
driver.switch_to.alert.accept()
But how can I click "OK"in the confirmation popup whenever it appears?Do I have hto keep checking every 30 seconds if the confirmation popup exists?
It looks like someone here had a similar question as you: How to check if an alert exists using WebDriver?
If you run driver.switch_to.alert when there is no alert actually present, you will receive a NoAlertPresentException. So, the basic idea is to write a method with a try / catch block that attempts driver.switch_to.alert, and returns true or false based on the presence of an exception.
Here's an example:
public boolean doesAlertExist()
{
try
{
driver.switch_to.alert();
return true;
}
catch (NoAlertPresentException e)
{
return false;
}
}
As far as your timing issue goes (alert pops up every 15ish minutes or so) -- you can try to write a wrapper method for driver.findElement() and element.click() that checks for the presence of the alert implicitly. Specific details about the method will depend on the project, but here's an example of something simple:
public IWebElement findElementWrapped(By by)
{
if (doesAlertExist())
{
driver.switch_to.alert().accept(); // accept the alert
return driver.findElement(by); // use selenium's standard findElement
}
else
{
// no alert exists, just find the element
return driver.findElement(by);
}
}
With this code, you can check for the alert every time you try to find an element on the page, but you only have to write the line of code once. You can use this method in action like this:
// check for alert, accept alert if it exists, get the desired web element
IWebElement myElement = driver.findElementWrapped(by);
Related
I need to write a test using cucumber for a course.
Scenario:
Login,
Select first item link,
Add item to the shopping cart,
Proceed shopping cart page,
Check the item on that list is correct,
Proceed to checkout,
Complete and Logout.
The point I don't understand is, do I need to open a feature file for each step and do I need to close and reopen the browser for each step? How should I do this? What kind of path should I follow?
(Note that I am a beginner and my English skills are limited, so I need a simple explanation.)
Yes, you should make a new file for each feature. For example, login.gherkin, upload.gherkin, logout.gherkin.
PS. Sorry, I didn't realize you said Python, but it's the same idea.
Each file should have a layout like this:
# Created by Mick Jagger at 1/13/2021
#login
Feature: Login
Scenario: Login to Website Homepage
When Launch website
Then Enter username and password
And Log in
Then make the corresponding step file like this:
package glue;
import cucumber.api.java.en.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import java.util.List;
public class LoginSteps extends Driver {
public String username = "username";
public String password = "password";
#When("Launch Website")
public void launch_website() throws Throwable {
driver.get("https://www.website.com/");
Thread.sleep(2000);
}
#Then("^Enter username and password$")
public void enter_credentials() throws Throwable {
driver.findElement(By.xpath("//input[#aria-label='Phone number, username, or email']")).sendKeys(username);
driver.findElement(By.xpath("//input[#aria-label='Password']")).sendKeys(password);
}
#And("^Log in$")
public void log_in() throws Throwable {
Thread.sleep(1000);
List<WebElement> buttons = driver.findElements(By.tagName("button"));
buttons.get(1).click();
Thread.sleep(2000);
driver.findElement(By.tagName("button")).click();
Thread.sleep(1000);
buttons = driver.findElements(By.xpath("//button[#tabindex='0']"));
buttons.get(1).click();
}
}
If you're new to browser automation, I recommend learning the "Page Object Model Methodology". Also, I stopped using this annoying framework, it's supposed to make things easier but to me it just adds extra work.
Essentially none of the traditional fixes are working. I have tried implicit and explicit waits, and waiting for the element to be clickable. As you can see below, the stack trace is a standard stale element exception, but the line of code beneath it (what throws the error), should be enough to stop the error I would think? Even stranger, the error does not throw consistently. I would be
selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document
add_account = wait.until(EC.element_to_be_clickable((By.XPATH,"//span[.='Add another account']" )))
Dirty and quick approach : Surround your code in a try block and upon encountering a StaleElementReferenceException discard the current reference you hold and replace it, possibly by locating the element again once it is attached to the DOM.
try:
add_account = wait.until(EC.element_to_be_clickable((By.XPATH,"//span[.='Add another account']" )))
add_Account.click()
except StaleElementReferenceException:
add_account = wait.until(EC.element_to_be_clickable((By.XPATH,"//span[.='Add another account']" )))
add_account.click()
You can enclose the above snippet inside an infinte while loop and have some kind of a flag to break out of it once you are able to click on the element.
Better approach : You can implement the AbstractEventListener Class provided by selenium webdriver API to have the before_find method wait for the following events to complete:
document.readyState to be equal to complete
window.jQuery.active to return false
Wait for angular js to complete its rendering
After implementing the AbstractEventListener, wrap your driver object with the implementing class.
ef_driver = EventFiringWebDriver(driver, EventListener())
class EventListener(AbstractEventListener):
def before_find(self, by, value, driver):
wait_for_everything_to_load(driver)
def wait_for_everything_to_load(driver):
"""
Waits for the DOM to load, angular js to complete
"""
try:
WebDriverWait(driver, 20).until(lambda x:
driver.execute_script("""
try {
if (document.readyState !== 'complete') {
return false; // Page not loaded yet
}
if (window.jQuery) {
if (window.jQuery.active) {
return false;
} else if (window.jQuery.ajax && window.jQuery.ajax.active) {
return false;
}
}
} if (window.angular) {
if (!window.qa) {
// Used to track the render cycle finish after loading is complete
window.qa = {
doneRendering: false
};
}
// Get the angular injector for this app (change element if necessary)
var injector = window.angular.element('body').injector();
// Store providers to use for these checks
var $rootScope = injector.get('$rootScope');
var $http = injector.get('$http');
var $timeout = injector.get('$timeout');
// Check if digest
if ($rootScope.$$phase === '$apply' || $rootScope.$$phase === '$digest' || $http.pendingRequests.length !== 0) {
window.qa.doneRendering = false;
return false; // Angular digesting or loading data
}
if (!window.qa.doneRendering) {
// Set timeout to mark angular rendering as finished
$timeout(function () {
window.qa.doneRendering = true;
}, 0);
return false;
}
return true;
} catch (ex) {
return false;
}
"""
)
)
except:
pass
You can try something like this :
while True:
try:
add_account = driver.find_element_by_xpath("//span[.='Add another account']")
add_account.click()
break
except StaleElementReferenceException:
print("stale element exception occured")
I am trying to automate my mobile web app.Every time it loads, it will ask "___ would like to use your location". User can either hit "don't allow" or "OK."
I am using self.driver.switch_to.alert.accept(), but it is accepting the default, which is "don't allow." How can I accept the "OK" instead of "don't allow"?
That method currently doesn't work for me either.
Did you try to locate "OK" button with Appium Inspector maybe?
I have the same pop up for location, and for me either of these work (xpath or accessibility ID) on iOS.
Something like:
allow_access_to_location_ios = (MobileBy.ACCESSIBILITY_ID, 'OK')
or
allow_access_to_location_ios = (MobileBy.XPATH, '//XCUIElementTypeButton[#name="OK"]')
then you call method:
self.wait_for_and_accept_alert(*self.allow_access_to_location_ios)
and method will wait for the element 15 seconds and click on it if it finds it otherwise it will print an error message:
def wait_for_and_accept_alert(self, *locator):
try:
WebDriverWait(self.driver, 15).until(lambda s: self.driver.find_element(*locator))
self.driver.find_element(*locator).click()
except TimeoutException:
print('Unable to find element')
For Accepting system alert create a method:
def accept_notification_alert(self):
print('Accept alert')
try:
self.driver.switch_to.alert.accept()
except WebDriverException:
print('Webdriver error: %s' % WebDriverException.msg)
For dismissing system alert you can create method:
def dismiss_notification_alert(self):
print('Dismiss alert')
try:
self.driver.switch_to.alert.dismiss()
except WebDriverException:
print('Webdriver error: %s' % WebDriverException.msg)
If a default approach (switch_to.alert.accept()) desn't work, you may try mobile gestures:
driver.execute_script('mobile: alert', {'action': 'accept', 'buttonLabel': <your button label here>});
I understood that you are asking for the device location alert. You can identify and use the Xpath of OK button to click. I have used the following code (in java) for an alert appears in the app.
driver.findElement(By.xpath("//UIAApplication[1]/UIAWindow[7]/UIAAlert[1]/UIACollectionView[1]/UIACollectionCell[1]/UIAButton[1]")).click();
The ActionChains is a very handy method when using Selenium.
It works really well, only thing i am missing is how to insert wait times between the Actions.
I will take the same example from the official google Selenium Documentation.
https://selenium.googlecode.com/git/docs/api/py/webdriver/selenium.webdriver.common.action_chains.html
menu = driver.find_element_by_css_selector(".nav")
hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()
What i am looking for is a way to insert wait times between the two actions
ActionChains(driver).move_to_element(menu)**(..wait some seconds)**.click(hidden_submenu).perform()
Thanks!
I tried this and seems working
from selenium import webdriver
action = webdriver.ActionChains(driver)
action.pause(3)
action.perform()
driver.close()
Note: Better answer
RobZ's answer is sufficient. The ActionChains class has a pause method that will do what was asked in the original question without requiring any subclassing or custom code.
Original answer
Here's a Python example based on Kim Homann's tip. It extends the ActionChains Selenium class to add a wait action.
import time
from selenium.webdriver import ActionChains
class Actions(ActionChains):
def wait(self, time_s: float):
self._actions.append(lambda: time.sleep(time_s))
return self
Then your test becomes:
Actions(driver) \
.move_to_element(menu) \
.wait(2) \
.click(hidden_submenu) \
.perform()
I don't know Python, but I think it's the same as in C#. I hope my code is readable for you.
You can create your own class ActionsEx deriving from Actions. Then you declare a method public Actions Wait(TimeSpan duration). Inside this method, you call AddAction(new SleepAction(duration));. AddAction() is a protected method of Selenium's Actions class, which is accessible only if you derive from this class.
SleepAction is a class implementing the IAction interface, which you have to create. It can look like this example:
public class SleepAction : IAction
{
public SleepAction(TimeSpan duration)
{
_duration = duration;
}
private TimeSpan _duration;
void IAction.Perform()
{
ToolBox.Sleep((int) _duration.TotalMilliseconds);
}
}
ActionsEx class:
public class ActionsEx : Actions
{
public ActionsEx(IWebDriver driver) : base(driver)
{
}
public Actions Wait(TimeSpan duration)
{
AddAction(new SleepAction(duration));
return this;
}
}
Then you can call an action chain like this:
var actions = new ActionsEx(driver);
var duration = TimeSpan.FromSeconds(1);
((ActionsEx)actions
.Wait(duration)
.MoveToElement(element))
.Wait(duration)
.Click()
.Build()
.Perform();
Simply import the time modul and use sleep whenever you need it:
from time import sleep
action = webdriver.ActionChains(driver)
action.move_to_element(menu)
sleep(5)
action.click(hidden_submenu).perform()
Hope this helps you a bit.
I believe the issue is that a delay executed outside of the ActionChain will be ignored once perform is called. Think of the chain like a queue of actions in sched: you could wait for hours and hours between adding items to the queue, but once you call run, each task will execute in simple sequence without a delay.
So, to create a delay inside the chain, I would use Selenium's pause method.
Docs here: http://selenium-python.readthedocs.io/api.html
I am trying to create my own Selenium class with custom functions so that test scripting will become a bit more intuitive and more robust in some scenarios, for my taste at least. One of my current tasks is to wrap all Selenium expected conditions (described here) so that eventually I will have a single function that looks something like that:
def waitForElement(self, elementName, expectedCondition, searchBy)
Where:
elementName - the name of the element I am looking for. That could be id, name, xpath, css, etc...
expectedCondition - this is where the Selenium expected condition is set. So that can be: element_to_be_clickable, visibility_of_element_located, etc...
The above function internally implements the standard Selenium WebDriverWait as follows:
try:
if expectedCondition == "element_to_be_clickable":
element = WebDriverWait(self.driver, defaultWait).until(EC.element_to_be_clickable((searchBy, elementName)))
elif expectedCondition == "visibility_of_element_located":
element = WebDriverWait(self.driver, defaultWait).until(EC.visibility_of_element_located((searchBy, elementName)))
All is good but I have a bit of trouble with passing the searchBy as a parameter. To remind, searchBy can be one of the following:
By.ID
By.NAME
By.CLASS_NAME
...
When I call this wrapper function from the main code, I do it with the below line:
self.waitForElement("elementName", "element_to_be_clickable", "By.NAME", "test")
So all parameters are passed as strings which is fine for everything except of the searchBy part.
So my question is: How can I pass the By.X part as a parameter to my function?
Hopefully I was able to describe my situation well. If I wasn't I will be happy to clarify.
Eventually I was able to solve this problem after asking this question. So in order to obtain the desired functionality, the above-mentioned method will look like this:
def waitForElement(self, elementName, expectedCondition, searchBy):
try:
if expectedCondition == "element_to_be_clickable":
element = WebDriverWait(self.driver, self.defaultWait).until(EC.element_to_be_clickable((getattr(By, searchBy), elementName)))
elif expectedCondition == "visibility_of_element_located":
element = WebDriverWait(self.driver, self.defaultWait).until(EC.visibility_of_element_located((getattr(By, searchBy), elementName)))
. . .
So it can be called like this:
self.waitForElement("elementName", "element_to_be_clickable", "NAME")
You can start like this:
Create main findElement method, that accepts By instance:
WebElement findElement(By by) {
try {
return driver.findElement(by);
} catch (NoSuchElementException e) {
logException("ERROR: Could not find - '" + by + "' on page " + driver.getCurrentUrl());
throw e;
}
Then create wait method, that uses the findElement method:
WebElement findElementAndWaitElementToPresent(By by, int timeoutInSeconds) {
try {
WebDriverWait wait = new WebDriverWait(driver, timeoutInSeconds);
wait.until(ExpectedConditions.presenceOfElementLocated(by));
return findElement(by);
} catch (TimeoutException e) {
logException("ERROR: Element is not present: " + by + " on page " + driver.getCurrentUrl());
throw e;
}
}
And pass By instance to the findElementAndWaitElementToPresent method:
findElementAndWaitElementToPresent(By.xpath("//your_xpath"), 10);
or
findElementAndWaitElementToPresent(By.name("name"), 10);
something like this is done in the framework i am working on/with
Issue with your code is that you are converting "By" datatype to string.
Simple way would be to pass it without quotes like below:
self.waitForElement("elementName", "element_to_be_clickable", By.NAME, "test")
Only additional thing you need to do is to add import as below for By in python module from where you are calling above method:
from selenium.webdriver.common.by import By