Northers
Northers

Reputation: 85

Wait for CSS property to change before asserting the change

I'm using python webdriver and I'm trying to make the system wait until the border colour of a password field has changed after a submit button has been clicked. The colour fades from one to the other and I can get the desired result using this: -

password_border_colour_after_click = ""
count = 0
while password_border_colour_after_click != "rgba(169, 68, 66, 1)":
    password_border_colour_after_click = self.driver.find_element_by_name("password").value_of_css_property('border-bottom-color')
    time.sleep(0.1)
    count += 1
    if password_border_colour_after_click == "rgba(169, 68, 66, 1)":
        break
    elif count > 50:
        break
assert password_border_colour_after_click == "rgba(169, 68, 66, 1)"

It feels a bit clunky and is probably awful code so I was thinking there must be a way to package it all up in a wait command, but I can't get the syntax right.

Is it possible?

Upvotes: 0

Views: 2028

Answers (2)

Tiberiu C.
Tiberiu C.

Reputation: 3513

TL;DR;

Selenium API has a "wait for ..." functionality :

http://selenium-python.readthedocs.io/waits.html

Implement a expected_condition and then combine it with wait

element = WebDriverWait(driver, 10)
             .until(custom_expected_condition((By.ID, 'search-input'), argA, argB))

Wait

Good to know is that "wait" runs synchronously (blocks the thread) and can be combined with one of the following, out of the box:

  • title_is
  • title_contains
  • presence_of_element_located
  • visibility_of_element_located
  • visibility_of
  • presence_of_all_elements_located
  • text_to_be_present_in_element
  • text_to_be_present_in_element_value
  • frame_to_be_available_and_switch_to_it
  • invisibility_of_element_located
  • element_to_be_clickable
  • staleness_of
  • element_to_be_selected
  • element_located_to_be_selected
  • element_selection_state_to_be
  • element_located_selection_state_to_be
  • alert_is_present

As you can see there is no element_has_style_value condition, however one can always extend the "wait" with its own implementation.

Out of the docs a custom expected condition looks like so:

class element_has_css_class(object):
  """An expectation for checking that an element has a particular css class.

  locator - used to find the element
  returns the WebElement once it has the particular css class
  """
  def __init__(self, locator, css_class):
    self.locator = locator
    self.css_class = css_class

  def __call__(self, driver):
    element = driver.find_element(*self.locator)   # Finding the referenced element
    if self.css_class in element.get_attribute("class"):
        return element
    else:
        return False

And its usage is:

# Wait until an element with id='myNewInput' has class 'myCSSClass'
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass"))

Possible solution

Putting everything together for an asset page like so : https://output.jsbin.com/xutipu

One can implement this:

class element_has_css_value(object):
  """An expectation for checking that an element has a particular css property and value.

  locator - used to find the element
  returns the WebElement once it has the particular css property and value
  """
  def __init__(self, locator, css_property, css_value):
    self.locator = locator
    self.css_property = css_property
    self.css_value = css_value

  def matchPropertyAndValue(self, css_property, css_value):

      return css_property == self.css_property and css_value  == self.css_value;

  def extractPropertyAndValue(self, cssStatement):
    keyValue = cssStatement.split(':')
    if len(keyValue) == 2:
        key = keyValue[0].strip();
        value = keyValue[1].strip();
        return (key, value)
    return (None, None)

  def findProperty(self, entries):
    foundProperty = False
    for entry in entries:
      (css_property, css_value) = self.extractPropertyAndValue(entry)
      if css_value is None:
          continue
      foundProperty = self.matchPropertyAndValue(css_property, css_value);
      if foundProperty :
          break;
    return foundProperty

  def __call__(self, driver):
    element = driver.find_element(*self.locator)   # Finding the referenced element
    styles = element.get_attribute("style")
    entries = styles.split(';')
    foundProperty = self.findProperty(entries);

    if foundProperty :
        return element
    else:
        return False

and use it like this :

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://output.jsbin.com/xutipu")
button = None;
try:
    button = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "search-button"))
    )
except e:
    print(e);
    driver.quit()

button.send_keys(Keys.RETURN)

try :
    wait = WebDriverWait(driver, 10)
    element = wait.until(element_has_css_value((By.ID, 'search-input'), "border-color", "rgb(169, 68, 66)"))
    assert element;
    print('Success');
finally:
    driver.quit()

Upvotes: 3

Marcel
Marcel

Reputation: 1463

I don't know how to do this in Python since I'm not that familiar with the language, but in C# you can create delegate functions that act like custom ExpectedConditions which you can use with the WebDriverWait class.

The WebDriverWait class waits n amount of seconds before throwing an exception.

Example (psuedo) code in C#:

public static class CustomExpectedConditions
{
    public static Func<IWebDriver, bool> ElementAttributeContains(By locator, string attributeName, string expectedValue)
    {
        return (driver) => driver.FindElement(locator).GetAttribute(attributeName).Contains(expectedValue);
    }
}

This allows you to use it like:

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
wait.Until(CustomExpectedConditions.ElementAttributeContains(By.Name("password"), "style", "rgba(169, 68, 66, 1)"));

I found this sample code for custom expected conditions in Python:

class element_has_css_class(object):
"""An expectation for checking that an element has a particular css class.

locator - used to find the element
returns the WebElement once it has the particular css class
"""
def __init__(self, locator, css_class):
  self.locator = locator
  self.css_class = css_class

def __call__(self, driver):
  element = driver.find_element(*self.locator)   # Finding the referenced element
  if self.css_class in element.get_attribute("class"):
      return element
  else:
      return False

# Wait until an element with id='myNewInput' has class 'myCSSClass'
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass"))

Hope this will get you started in the right direction

Upvotes: 1

Related Questions