Search code examples
selenium-webdriverweb-scrapingcss-selectors

How to change currency in booking by selenium


I'm trying change currency on USD in booking.com website by Selenium but I got a little problem with "click" on USD sign. My code always set first Currency from list, which is EURO. I see the problem in my find_elemnt CSS selector.

Now I have CSS like below:

        currency_element.click()
        selected_currency = self.find_element(
            By.CSS_SELECTOR,'button[data-testid="selection-item"][class="a83ed08757 aee4999c52 ffc914f84a c39dd9701b ac7953442b"]'
        )
        selected_currency.click()

enter image description here

I tried use Xpath, but in result I recived Indian Rupies .

Thanks a lot for help

I find solution like code belowe

>     def change_currancy(self,currency=None):
        time.sleep(5)
        currency_element = self.find_element(
            By.XPATH,"/html/body/div[2]/div/header/nav[1]/div[2]/span[1]/button"
        )
        currency_element.click()
        selected_currency = self.find_elements(
            By.CSS_SELECTOR,'button[data-testid="selection-item"][class="a83ed08757 aee4999c52 ffc914f84a c39dd9701b ac7953442b"]'
        )
        for element in selected_currency:
            try:
                curr = element.text.split("\n")[-1]
                if curr == "USD":
                    time.sleep(4)
                    element.click()
                else:
                    pass
            except:
                pass

Solution

  • For my example, I will use XPath instead of CSS selector.

    Assuming you have already clicked button to select your currency, there will be a popup overlaid over your page. This popup appears NOT to be inside an iframe. If this was the case, you would've need to invoke the Selenium function to find the iframe and switch to it in order to locate the components inside the frame you need to interact with and once the iframe is dismissed, you will have to switch back to the parent frame. This is not the case here. The popup is just a simple component that was simply hidden until the currency button is clicked.

    Popup showing the currencies

    The path to each of the currency buttons is quite complex. At a high level, this popup is divided into two sections: The suggested currencies for you (boxed in green), and the all currencies sections (boxed in red). For this example, the assumption will be that we just need to interact with the "All currencies" section. Since we expect (up to this point), that the path to this section is to remain constant for all our tests, I will construct an XPath that will not contain any variables. In other words, the XPath string will contain no wildcards to be replaced with a value during our tests. The XPath to this section is simply: //div[@data-testid='All currencies']

    Now that we have moved to the correct section of the page, we need to identify the item(s) to click. The array of currencies is built on the page using a series of unordered lists (ul tag name) that represent a row. Each one of these rows (first one boxed in blue), contain a number of list items (li tag name), each one which eventually will have identifiable text describing the currency (i.e. "U.S. Dollar"). Because we will not know the exact row where the desired currency will be, we will append the row element (ul) to the XPath using relative path (//). Therefore, my new XPath string is now

    //div[@data-testid='All currencies']//ul
    

    Since all the list items are an immediate child of the row element, we will append it to the XPath using single slash:

    //div[@data-testid='All currencies']//ul/li
    

    The remaining part of the XPath is to match the text for the currency. We should not use array notation for this. What we should do instead is to identify the component that contain the text. Let's assume that we want to change the currency to "Argentine Peso." A hard-coded solution for that looks like this: //div[@data-testid='All currencies']//ul/li//span[text()='Argentine Peso']. The reason I used relative path to the span element containing the text is simply to shorten the path to this component. The list of components between the li and the span is really inconsequential.

    Our hard-coded solution looks like this

    //div[@data-testid='All currencies']//ul/li//span[text()='Argentine Peso']
    

    In the above XPath, the part that changes is only the currency name (or text). You can wildcard this so that you could replace at test time the wildcard with the exact text for the currency. For example, if instead of Argentine Peso I need to select "Israeli New Shekel", I could replace the text in my hard-coded solution and it should work:

    //div[@data-testid='All currencies']//ul/li//span[text()='Israeli New Shekel']
    

    And this works as you can see in the image below XPaths for the two currencies

    Now, to finalize my solution, I will replace the hard-coded currency description with some character (or sequence of characters) that are not in the rest of the XPath string, so that when I call a "replace substring" function, I could simply say "replace wildcard with this currency description" and the rest should work. For example:

    //div[@data-testid='All currencies']//ul/li//span[text()='%']
    

    In a Java function, you can do something like this to replace the wildcard with the currency description:

    String currency = "Argentine Peso";
    String xpath = "//div[@data-testid='All currencies']//ul/li//span[text()='%']";
    xpath.replace("%", currency);
    

    The sequence of steps above will replace the wildcard substring (%) with the value of the currency variable ("Argentine Peso"). In the context of a Selenium test, you should be able to adapt this solution. If you are using something like Cucumber, you can easily adapt this to take the currency in your test case and replace the wildcard in the XPath string.

    If you are not using Selenium in the context of a test but in some automation initiative, the same can be done. You could embed similar logic into your (let say JavaScript function) and do this replacement at runtime. The key is to have the wildcarded XPath string already stored in some property, and then morph it into whatever you need this particular function to do.

    UPDATE: In my haste to finish a chore in the house, I neglected to point out one important detail. While I was able to find the wanted currency, the XPath might not return a clickable component. In the XPath between the li and the span containing the text, there is a button element. Therefore, my previous statement that the elements in between these two were inconsequential is misleading. They are inconsequential for location scheme only. So, if this span element is not clickable, I must work upwards in the DOM and locate the clickable component for this located span. In fact, even if it is clickable, it might be better to refine the XPath to locate the button element instead.

    The button element corresponding to the located span node

    Examining the path, we can see that button is an ancestor of the span element just located. Fortunately, there is a very easy way to navigate upwards to point to an element higher in the component hierarchy. For this, we will use the XPath axis ancestor. FOR THIS SPECIFIC CASE, there is only ONE ancestor that is a button. Therefore

    //div[@data-testid='All currencies']//ul/li//span[text()='Argentine Peso']//ancestor::button
    

    will return the button element that is an ancestor of the located span element Button element