How can I automate opening a Dash dbc.AccordionItem (tab) with Python Selenium?

I’ve been trying to automate the testing of my app with Python Selenium.

The app sits entirely inside a Dash dbc.Accordion.

There are three dbc.AccordionItem elements. Each has a separate “item_id.”

My app takes a culinary recipe as a block of text and returns a nutritional profile,

The code works fine until I need uncollapse the second dbc.AccordionItem. To keep this brief, I cannot figure out how to uncollapse the second tab and click the submit button with Python-Selenium.

I should be able to do it in many ways (via id, class name, link etc…). When I try to inspect the tab bar in the Chrome browser to find the identifying information I need, I find the results confusing.

By the way, I don’t receive any error messages .

Does anyone have any idea how to solve this problem?

If you have the time, you can inspect the html and css by going to this ip address, where its deployed as a Docker Image on Amazon AWS Elastic Container Services. You can type in my username and password.

(I don’t know why I felt the need to have credentials, excepts its a rough draft. Its functional), I just was not ready for people to see it). Thank you for reviewing my question.

Your feedback is much appreciated.

Robert

P.S. I share snippets of the Python-Selenium code below, but I will gladly share the whole thing as requested.

Its not that long - about a page.

1. This is the IP address:

http://44.201.177.119:8050/

username: robertpfaff
password: adam2326

2. Here is the relevant Selenium code:

# Moving to second tab: edit initial results

# Find second tab in accordion (second tab).

try:

    open_tabtwo = WebDriverWait(driver, 10).until(

        EC.presence_of_element_located(

            (By.ID, "???"))

    )

finally:

    print("Unable to locate: ", open_tabtwo)

    pass

open_tabtwo.click()

3. Here, I will paste the relevant Dash HTML for the second tab:

dbc.AccordionItem(  # Step 2: Edit initial results.

                    [

                        html.Div("Please edit initial results.",

                                 className="mb-4"),

                        dash_table.DataTable(

                            id="nutrients-review-datatable",

                            columns=[{'id': i, 'name': i} for i in [

                                "Index", "Amount", "Unit", "Modifier", "Ingredient", "ID", "Grams", "Multiplier"]],

                            style_cell={'textAlign': 'left', 'padding': '10px', 'fontSize': 14,

                                        'font-family': 'sans-serif', 'fontWeight': 'bold'},

                            style_header={'backgroundColor': '6eafde', 'fontWeight': 'bold', 'font-size': 14,

                                          'border': '1px solid black', 'align': 'left', 'color': 'black'},

                            style_data={'color': 'black'},

                            style_data_conditional=[

                                {'if': {'row_index': 'odd'}, 'backgroundColor': 'rgb(248,248,248)'}],

                            data=None,

                            editable=True,

                            row_deletable=True,

                            # tooltip,

                            hidden_columns=['Index', 'ID',

                                            'Grams', 'Multiplier'],

                            css=[{"selector": ".show-hide",

                                  "rule": "display: none"}],

                            sort_action='native',

                            page_action='native',

                            page_size=15,

                            style_table={'height': '500px',

                                         'overflowY': 'auto'},

                        ),

                        # html.Div([

                        # dbc.Button("Submit Recipe", id="recipe-textarea-submit-button", style={'margin-bottom': '10px'}, n_clicks=0, outline=True, color='primary'),

                        # dbc.Spinner(html.Div(id="loading-output1"), spinner_style={"width": "3rem", "height": "3rem"}),

                        dbc.Button(

                            "Submit Edits",

                            id="nutrients-review-datatable-submit-button",

                            className="me-2",

                            n_clicks=0,

                            outline=True, color='primary'

                        ),

                        dbc.Spinner(html.Div(id="loading-output2"),

                                    spinner_style={"width": "3rem", "height": "3rem"}),

                    ],

                    title="Step 2: Edit Initial Results",

                    item_id="acc_datatable",

                ),

We do some integration testing of the accordion in the dash-bootstrap-components repo. It’s using Dash Testing which is built on Selenium. Perhaps comparing against that will help? Link

The relevant bits would be something like the following

accordion = runner.find_element("#accordion")

items = accordion.find_elements_by_class_name("accordion-item")
    
# Click the second section
items[1].find_element_by_class_name("accordion-button").click()
1 Like

Thank you for this.

To make sure I understand, you store the whole accordion in the variable “accordion.” Secondly, you store ALL the AccordionItems (tabs) in the variable “items,” which is a list. Then, you reference the second item in that list - or the second Accordion item by class name - which is okay because you’ve already identified it as the second item.

Am I following the logic correctly?

When you say “Dash Testing,” do you mean pytest-dash?

I just want to be sure I know what to import. I am familiar with pytest-dash, but I am not familiar with “import dash.testing.wait as wait” as shown in the link you provided.

This makes sense to me.

It could work.

Thanks again,

Robert

Yep, I think you got the logic correct!

I don’t mean pytest-dash (or at least I don’t think I do?). Dash Testing is an optional part of Dash that includes utilities for testing. You can install with pip install "dash[testing]". The docs are here

1 Like

This makes me so happy!

I am not entirely sure how or why it works.

But I found a working solution in Javascript to the problem preventing me from automating the opening of the second AccordionItem (banner or tab) in a Dash-Python dbc.Accordion, so I could proceed to submit any edits to the original results.

For some reason, you cannot use the standard Python-Selenium variable.click() method.

Please see code under # Find second header banner by xpath and click.

It works.

Here is the code:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
import time

PATH = "C:\Program Files (x86)\chromedriver.exe"
service = Service(PATH)
driver = webdriver.Chrome(service=service)
driver.maximize_window()
driver.get("http://localhost:8050/")
action = ActionChains(driver)

# Confirm connected to web page:
page_title = driver.title
print("Connected to Web Page: ", page_title)

time.sleep(5)

# Find recipe text area.
recipe_textarea = driver.find_element(by=By.ID, value='recipe-textarea')

# Load test data into recipe text area.
recipe_textarea.send_keys("2 cups lentils")

# Find submit button
submit_recipe = driver.find_element(
    by=By.ID, value='recipe-textarea-submit-button')

# Click submit button.
submit_recipe.click()

# Scroll down to element
driver.execute_script("window.scrollBy(0,500)", "")

# Find second header banner by xpath and click
tab_two = driver.find_element_by_xpath(
    "//button[normalize-space()='Step 2: Edit Initial Results']")
driver.execute_script("arguments[0].click();", tab_two)

# Find submit edits button.
submit_edits = driver.find_element_by_id(
    "nutrients-review-datatable-submit-button")

# Click submit edits button
submit_edits.click()