Using Selenium Webdriver to interact with Stripe Card Element iFrame - Cucumber/Selenium Java
Asked Answered
F

5

6

I have an form that I want to automate using Cucumber and Selenium Webdriver in Java - in this form, we have a card element which we use from Stripe. We call the div, and stripe does the rest. I'm unsure if this is an iFrame, but when I use the

Hooks.driver.findElement(By.xpath("xpathOfTheCardNumberField")).sendKeys("123");

command, it does not interact with it and returns the "Unable to locate element" error in the console log.

I have asked our front-ender to perhaps try and add some ID's or Name tags to the fields, but he informs me that he cannot interact with the markup for the fields inside of the card element, only the card element itself - as Stripe deal with everything else.

Attached is a picture of the card element, as well as the markup for the card element in question.

Is it possible to get Selenium to interact with this element?

Any help is greatly appreciated. Card element front end

Mark up for the card element

Fletafletch answered 15/2, 2018 at 10:58 Comment(0)
F
3

I've actually figured this out myself, so I'm going to answer the question and close it off in case anyone else is having issues with it.

I think this is a blanket method that can be used for any iframes, not just Stripe.

Firstly, you must tell your webdriver to switch frames to the iframe you are trying to access:

Hooks.driver.switchTo().frame(Hooks.driver.findElement(By.xpath("xpathOfIframe")));

Then, you can create webElements for the things within that iframe, and interact with them:

WebElement creditcardNumber = Hooks.driver.findElement(By.name("cardnumber"));
creditcardNumber.sendKeys("1234567890000066");
Fletafletch answered 15/2, 2018 at 11:45 Comment(0)
G
6

Additional follow-up to the accepted answer by Fabio for completeness.

self.browser = webdriver.Chrome()

# fill in the other fields as usual (i.e. self.browser.find_element_by_id(...))

# When you arrive at the iframe for the card number, exp. date, CVC, and zip:
self.browser.switch_to.frame(frame_reference=self.browser.find_element(By.XPATH, '//iframe[@name="__privateStripeFrame3"]'))

# This switches to the iframe, which Selenium can now start selecting elements from.

# The remaining form elements can be found by name

self.browser.find_element_by_name('cardnumber').send_keys('4242 4242 4242 4242')

# And find the other elements the same way as above (exp-date, cvc, postal).
# Finally, switch back to the default content to select the submit button

self.browser.switch_to.default_content()
self.browser.find_element_by_tag_name('button').click()
Gabble answered 20/4, 2018 at 4:54 Comment(0)
F
3

I've actually figured this out myself, so I'm going to answer the question and close it off in case anyone else is having issues with it.

I think this is a blanket method that can be used for any iframes, not just Stripe.

Firstly, you must tell your webdriver to switch frames to the iframe you are trying to access:

Hooks.driver.switchTo().frame(Hooks.driver.findElement(By.xpath("xpathOfIframe")));

Then, you can create webElements for the things within that iframe, and interact with them:

WebElement creditcardNumber = Hooks.driver.findElement(By.name("cardnumber"));
creditcardNumber.sendKeys("1234567890000066");
Fletafletch answered 15/2, 2018 at 11:45 Comment(0)
G
2

The current Stripe configuration puts cardnumber, the expiry date (exp-date) and cvc in different iframes. Unlike before, all are in one iframe and the solution above worked.

I was able to update the card information by just transferring to one iframe. Then the Stripe changes came and the front-end developer updated the payment web page. Then came the 'Unable to locate element ...' error for the expiry date and the cvc. Likewise, I asked the developer if he can put id's or names for the 3 inputs. He said, he cannot do it and Stripe makes the payment screen.

Normally, when you inspect in Firefox or in Chrome the credit card infos, you will see what iframe the inputs belong to.

In our web page, the cardnumber is in the __privateStripeFrame5. The exp-date is in the __privateStripeFrame6, and the cvc in the __privateStripeFrame7.

Here's the code that solved my dilemma in updating/testing the card infos using selenium python (java has similar methods/functions):

# has_zip_code - True or False
# added params for flexibility
# exp_date     - expiration date
# cvc_val      - 3-digit number
def enter_card_details_and_submit(driver, card_num, exp_date, cvc_val, has_zip_code):

    #print("\n{} -------------------------------------------\n".format(PI_WIN))
    print("Use card number ... {}".format(card_num))
    print(card_num)
    driver.switch_to.frame(frame_reference=wait_and_get_elem_by(driver, By.NAME, "__privateStripeFrame5"))
    card_num_text = wait_and_get_elem_by(driver, By.NAME, "cardnumber")
    card_num_text.click()
    time.sleep(1)
    card_num_text.send_keys(card_num)
    driver.switch_to.default_content()

    print("\nEnter expiry month/year ...")
    iframe6 = wait_and_get_elem_by(driver, By.NAME, "__privateStripeFrame6")
    driver.switch_to.frame(iframe6)
    exp_dt = wait_and_get_elem_by(driver, By.NAME, "exp-date")
    exp_dt.click()
    time.sleep(1)
    exp_dt.send_keys(exp_date)
    driver.switch_to.default_content()

    print("\nEnter CVC ...")
    driver.switch_to.frame(frame_reference=wait_and_get_elem_by(driver, By.NAME, "__privateStripeFrame7"))
    cvc = wait_and_get_elem_by(driver, By.NAME, "cvc")
    cvc.click()
    time.sleep(1)
    cvc.send_keys(cvc_val)
    .
    .
    .

wait_and_get_elem_by() is an internal function that finds the element but with wait() function in it. You can use find_element_by_name().

The cardnumber, exp-date, and cvc are updated using selenium (python):

Stripe Credit Card Information Updated Using Selenium (python)

Garceau answered 31/10, 2019 at 23:39 Comment(0)
I
2

For me this works right now. I found out that the Name on Card is not an iframe and it must be found by chrome.webdriver


driver.switch_to.frame(frame_reference=driver.find_element(By.XPATH, '/html/body/div[1]/div/main[2]/section/div/div[2]/div/article/div/form/fieldset[3]/div[2]/div/div/div/iframe'))
creditNumber = driver.find_element_by_name("cardnumber").send_keys("4242 4242 4242 4242")
time.sleep(1)

driver.switch_to.default_content()

driver.switch_to.frame(frame_reference=driver.find_element(By.XPATH, '/html/body/div[1]/div/main[2]/section/div/div[2]/div/article/div/form/fieldset[3]/div[3]/div[2]/div[1]/div/div/iframe'))
expiryDate = driver.find_element_by_name("exp-date").send_keys("1223")

driver.switch_to.default_content()

driver.switch_to.frame(frame_reference=driver.find_element(By.XPATH, '/html/body/div[1]/div/main[2]/section/div/div[2]/div/article/div/form/fieldset[3]/div[3]/div[2]/div[2]/div/div/iframe'))
cvc = driver.find_element_by_name("cvc").send_keys("345")

driver.switch_to.default_content()
time.sleep(1)

nameOnCard = driver.find_element_by_name("name_on_card").send_keys("Testing")
Intercellular answered 10/6, 2021 at 10:37 Comment(1)
This still works in 2023, but only need to switch the Stripe iframe once, no need to switch back to switch_to.default_content() and no need to time.sleep(1). I find the Stripe iframe with the following css selector: "div.__PrivateStripeElement iframe"Twelvemo
D
0

The main issue was navigating through multiple iframes. Stripe's payment pages are designed to enhance security, often using multiple nested or dynamically loaded iframes, which makes it challenging to find elements directly.

Here's the solution that worked for me:

List All Iframes: I started by listing all iframes on the page to identify which one contained the desired element. Switch to the Correct Iframe: I iterated through all iframes to find the one containing the cardNumber input field. Wait for Visibility: I ensured that the iframe and the elements were fully loaded and visible before interacting with them. Here's the generic code that I used:

public void inputPaymentDetails(String numCard, String dataCard, String cvcCard, String nameCard) throws Exception {
    try {
        // Ensure all elements are loaded
        Thread.sleep(4000); // Replace with WebDriverWait if possible for better handling

        driver.switchTo().defaultContent(); // Switch back to the main context
        List<WebElement> iframes = driver.findElements(By.tagName("iframe"));
        System.out.println("Total iframes found: " + iframes.size());

        for (int i = 0; i < iframes.size(); i++) {
            try {
                WebElement iframe = iframes.get(i);
                System.out.println("Checking iframe index: " + i);

                // Switch to current iframe
                driver.switchTo().frame(iframe);

                // Try to find the card number field in the current iframe
                List<WebElement> elements = driver.findElements(By.xpath("//*[@id='cardNumber']"));
                if (elements.size() > 0) {
                    System.out.println("'cardNumber' field found in iframe index: " + i);
                    break;
                } else {
                    System.out.println("'cardNumber' field NOT found in iframe index: " + i);
                }

                driver.switchTo().defaultContent(); // Switch back to the main content if not found
            } catch (NoSuchElementException e) {
                System.err.println("Error while checking iframe index: " + i + " - " + e.getMessage());
            }
        }

        // Interact with the found card number field
        WebElement cardNumberField = driver.findElement(By.id("cardNumber"));
        if (cardNumberField != null) {
            System.out.println("Found 'cardNumber' field, entering the card number...");
            cardNumberField.sendKeys(numCard);
        } else {
            System.out.println("'cardNumber' field not found within the iframe.");
        }

        // Repeat similar steps for expiration date and CVC fields if they are in different iframes

        // Switch back to main content
        driver.switchTo().defaultContent();

        // Input name on card
        WebElement nameField = driver.findElement(By.name("name_on_card"));
        if (nameField != null) {
            System.out.println("Found 'name_on_card' field, entering the cardholder's name...");
            nameField.sendKeys(nameCard);
        } else {
            System.out.println("'name_on_card' field not found in the main context.");
        }

        // Optionally click the submit button or take further actions

    } catch (NoSuchElementException e) {
        System.err.println("Error: Element not found - " + e.getMessage());
    } catch (Exception e) {
        System.err.println("Error during the execution of inputPaymentDetails method: " + e.getMessage());
    }
}
Digenesis answered 18/10 at 20:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.