2026, Jan 10 05:00

Automating Power BI Slicers with Selenium: Reliable Drag-Scroll to Reach Virtualized Months

Learn how to select months in a virtualized Power BI slicer using Selenium. Simulate drag scrolling with ActionChains to load hidden items when scrollTop fails.

Automating a Power BI report often hits the same wall: slicers that render only what’s on screen. When the month list is longer than the visible viewport, anything below the fold isn’t instantiated yet, so a naive scroll doesn’t reveal new items. If you need to select a specific month on the second page of a public Power BI dashboard, you have to trigger the exact interaction the UI expects.

Reproducing the issue

The flow is straightforward: open the report, navigate to the second page, open the date slicer, expand a year, then try to scroll to a month that’s out of view. The code below shows the setup and the failing attempts to scroll.

# Selenium setup (Edge)
from selenium import webdriver
from selenium.webdriver.edge.options import Options as EdgeOptions
from selenium.webdriver.edge.service import Service as EdgeService
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
# Driver/service
edge_bin = r"d:\dev\selenium\msedgedriver.exe"
svc = EdgeService(edge_bin)
edge_opts = EdgeOptions()
edge_opts.add_experimental_option("detach", True)
edge_opts.add_experimental_option("excludeSwitches", ["enable-automation"])
edge_opts.add_experimental_option("useAutomationExtension", False)
edge_opts.add_argument("--disable-blink-features=AutomationControlled")
edge_opts.add_argument("--inprivate")
browser = webdriver.Edge(service=svc, options=edge_opts)
# Navigate
target_url = "https://app.powerbi.com/view?r=eyJrIjoiZWIzNDg3YzUtMGFlMC00MzdmLTgzOWQtZThkOWExNTU2NjBlIiwidCI6IjQ0OTlmNGZmLTI0YTYtNGI0Mi1iN2VmLTEyNGFmY2FkYzkxMyJ9"
browser.get(target_url)
# Go to next page
browser.find_element(By.XPATH, '//button[@aria-label="Próxima Página"]/i').click()
# Open date slicer (icon is located)
browser.find_element(By.XPATH, '//div[@class="slicer-dropdown-menu"]/i')
# Expand months under a specific year
browser.find_element(
    By.XPATH,
    '//div[@class="slicerItemContainer" and @title="2024"]/div[@class="expandButton"]'
).click()
# Attempt 1: direct scrollTop (no effect on rendering)
pane = browser.find_element(By.XPATH, '//div[@class="slicerContainer"]')
browser.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", pane)
# Attempt 2: key-based scroll on the scroll bar (ElementNotInteractableException)
bar = browser.find_element(By.CLASS_NAME, "scroll-bar")
bar.send_keys(Keys.DOWN)

In practice, the months below March remain unreachable. Directly changing scrollTop doesn’t trigger the UI’s internal handlers, and sending keys to the scrollbar fails with ElementNotInteractableException. A CSS transform to shift visuals also doesn’t help, because it only moves pixels and doesn’t cause new items to render.

What’s actually happening

The slicer list is virtualized: only visible entries are rendered. Scrolling must fire the component’s internal event pipeline so it decides to render and mount the next set of items. Programmatically manipulating the container (scrollTop) or applying a transform moves the viewport but doesn’t produce the required events. Targeting the scrollbar with the keyboard isn’t enough either; the element you hit isn’t interactable in the way the component expects.

The reliable fix: simulate a real drag scroll

Triggering a genuine mouse-driven scroll does the job. Using ActionChains to hover the scrollable area, click-and-hold, then drag by an offset causes the component to load the next items. The sequence move_to_element → click_and_hold → move_by_offset → release maps to how a human scrolls that custom list.

# ===== IMPORTS =====
from time import sleep
from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
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 import ActionChains
# ===== SETUP OPTIONS =====
opts = ChromeOptions()
opts.add_argument("--start-maximized")
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
opts.add_argument("force-device-scale-factor=0.95")
browser = webdriver.Chrome(options=opts)
waiter = WebDriverWait(browser, 10)
def run_insights(year_label: str, month_index: int) -> None:
    link = "https://app.powerbi.com/view?r=eyJrIjoiZWIzNDg3YzUtMGFlMC00MzdmLTgzOWQtZThkOWExNTU2NjBlIiwidCI6IjQ0OTlmNGZmLTI0YTYtNGI0Mi1iN2VmLTEyNGFmY2FkYzkxMyJ9"
    browser.get(link)
    # Wait for page navigation
    waiter.until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'div[aria-label="Mercado Page navigation . Mercado"]')))
    # Go to the second page
    waiter.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#embedWrapperID>div.logoBarWrapper>logo-bar>div>div>div>logo-bar-navigation>span>button:nth-child(3)'))).click()
    # Open the date slicer dropdown
    waiter.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#pvExplorationHost > div > div > exploration > div > explore-canvas > div > div.canvasFlexBox > div > div.displayArea.disableAnimations.fitToPage > div.visualContainerHost.visualContainerOutOfFocus > visual-container-repeat > visual-container:nth-child(6) > transform > div > div.visualContent > div > div > visual-modern > div > div > div.slicer-content-wrapper > div>i'))).click()
    # Expand a specific year
    waiter.until(EC.presence_of_element_located((By.XPATH, f'//div[@class="slicerItemContainer" and @title="{year_label}"]/div[@class="expandButton"]'))).click()
    sleep(3)  # allow dropdown to finish its animation
    # Scroll area inside the dropdown
    drag_area = browser.find_element(By.CSS_SELECTOR, 'div[id^="slicer-dropdown-popup-"]>div>div>div:nth-child(2)>div>div:nth-child(3)')
    # Perform a real drag to trigger virtualization
    mouse = ActionChains(browser)
    mouse.move_to_element(drag_area).click_and_hold().move_by_offset(0, 100).release().perform()
    sleep(2)
    # Pick the month by its aria-posinset
    browser.find_element(By.XPATH, f'//div[@class="slicerItemContainer" and @aria-posinset="{month_index}"]').click()
    sleep(2)
run_insights('2022', 1)

The key is that the drag initiates the component’s own scroll logic. Adjusting the move_by_offset vertical distance controls how far the list advances. After that, selecting the month by its aria-posinset works as expected.

Why this matters

Embedded BI surfaces and modern dashboards frequently use virtualized lists and custom controls. They don’t react to plain DOM property changes or cosmetic transforms; they react to input events that their composition layer understands. If your automation doesn’t produce those events, nothing new renders and your scraper never reaches the data you need.

Takeaways

When a dropdown or slicer renders only visible nodes, treat scrolling as an interaction, not a style tweak. Wait for the right container to exist, move the pointer over it, click-and-hold, and drag by offset until the target appears. Relying on scrollTop or CSS transforms won’t trigger rendering, and sending keys to non-interactable parts won’t help either. With ActionChains you can stay close to how a human drives the UI and unlock the rest of the list reliably.