Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

After a Selenium drag-and-drop action, locate the new element only after the application has rendered it. The reliable sequence is: perform the drag, wait for an observable post-drop condition, then find the element with a stable locator. A completed drag_and_drop call means Selenium finished sending pointer actions; it does not prove that the page’s asynchronous JavaScript has finished updating the DOM.

The reliable three-step pattern

  1. Drag: identify the source and destination and perform the action.
  2. Wait: wait for the resulting DOM or application state, not an arbitrary number of seconds.
  3. Find and verify: query the current DOM with a stable locator and assert that the expected state exists.

Selenium locators operate on the DOM that exists when the command runs. If the destination adds an item after an XHR, animation, framework render, or other callback, a lookup made immediately after the drag can legitimately find nothing. This is the race between the application becoming ready and the next automation command.

A complete Python example

The following Selenium 4-style example waits for the source and target to be visible, performs the drag, then waits for the post-drop item. The data-testid values are examples; replace them with attributes from your page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)

source = wait.until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='source']")
    )
)
target = wait.until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='target']")
    )
)

ActionChains(driver).drag_and_drop(source, target).perform()

# Use a selector that identifies the item created by this drop.
added = wait.until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='added-item']")
    )
)
assert added.is_displayed()

Use presence_of_element_located rather than visibility_of_element_located when the next assertion only requires a node in the DOM. Selenium’s visibility condition requires the element to be present and have nonzero width and height, so it is the appropriate condition before clicking, reading rendered text, or taking an interaction-dependent action.

Use a locator for the new node, not a pre-drop handle

An element that did not exist before the drag cannot be located before the drag. Define its selector from the destination markup, a stable identifier, or a distinctive state attribute. Avoid generated CSS classes, changing list indexes, and text that is reused by unrelated items when a data-* attribute, ID, or scoped selector is available.

Scope the search when several items match

If the page can contain multiple matching items, locate the destination container first and search within it, or wait for an expected count or state. Accepting the first global match can pass against an old item in another column.

destination = wait.until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='target']")
    )
)
new_item = WebDriverWait(destination, 10).until(
    lambda container: container.find_element(
        By.CSS_SELECTOR, "[data-testid='added-item']"
    )
)

Alternatively, wait until the destination contains the expected number of children or until an attribute such as data-state="complete" appears. The condition should express what your test actually needs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Presence versus visibility

Condition Use it when What it proves
presence_of_element_located The node only needs to exist for a DOM assertion or later operation. A matching element is attached to the DOM.
visibility_of_element_located The next step needs a rendered, visible element. A matching element is attached and has nonzero dimensions.

Do not use visibility as a generic delay. If a framework intentionally inserts a hidden node and reveals it later, visibility is the meaningful condition. If hidden state is acceptable, presence avoids waiting for a condition your test does not require.

When the drop replaces an existing node

Some applications do not append a child. They replace the source, target, or an entire list during the drop. A previously stored WebElement then refers to a detached DOM node. Selenium reports this as a stale element reference; the old object does not automatically follow its replacement.

If the replacement transition matters, wait for the old reference to become stale, then find the replacement with a locator:

old_target = wait.until(
    EC.presence_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='target']")
    )
)

ActionChains(driver).drag_and_drop(source, old_target).perform()

wait.until(EC.staleness_of(old_target))
replacement = wait.until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='target']")
    )
)

Use staleness_of only when you expect detachment. For an append-only update, wait directly for the new locator. Reusing an old handle in a loop is not a substitute for re-finding the current element.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing and executing the drag operation

Selenium’s Actions API models drag_and_drop(source, target) as click-and-hold on the source, movement to the target, and release. The API also supports dragging by an offset from the source. Use an offset when the application requires a particular grip point or when the target’s center is not a valid drop zone.

ActionChains(driver).drag_and_drop_by_offset(source, 120, 40).perform()

If the convenience action does not trigger the application, investigate the page’s expected pointer sequence, hit area, overlays, and browser behavior before changing waits. A longer sleep cannot repair an interaction that never reaches the widget’s required event path. Confirm that the destination actually changes before debugging the post-drop locator.

Verify application state, not just command completion

A drag command returning without an exception is not proof that the item was accepted. Add an assertion tied to the business result:

  • the expected item appears inside the destination container;
  • the destination’s item count increases to the expected value;
  • a post-drop attribute, status, or class changes;
  • the source is removed or marked as assigned when that is the application’s contract;
  • the item’s text or identifier matches the item that was dragged.

These checks distinguish a successful pointer sequence from a successful application operation and make failures diagnosable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why an immediate lookup fails

The page updates after the command returns

Modern pages can schedule rendering after navigation and after user actions. Selenium may issue the next lookup while the application is still processing the drop. An explicit wait polls until the required condition is true or the timeout expires.

The selector describes the old DOM

Inspect the current markup after a successful manual drop. The inserted item may use a different attribute, be nested under a new container, or receive a state marker only after processing. Update the locator to match that actual structure.

The match is ambiguous

Repeated cards, virtualized lists, and multiple columns can produce several matches. Scope by destination and identify the dragged record by a durable key rather than position.

The node is in a different DOM context

An iframe requires switching into the correct frame before locating the result. A shadow root requires querying through that component’s shadow DOM. These are diagnostic branches: inspect the target page to determine whether either applies.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Timeout and stale-element troubleshooting

Symptom Likely cause Fix
Timeout waiting for the new selector The drop did not change state, or the selector is wrong. Assert destination state, inspect the post-drop DOM, and choose a stable locator.
Element exists but visibility wait times out The node is hidden, collapsed, or covered during an animation. Use presence if hidden state is acceptable; otherwise wait for the application’s visible state.
Stale element reference The framework replaced the node. Wait for staleness when appropriate and re-find the replacement by locator.
Wrong item is found Global selector matches old or unrelated items. Scope to the destination and match an ID, text, or state tied to the dragged item.
Drag appears to do nothing Wrong hit area, overlay, unsupported event sequence, or invalid source/target state. Check the destination before changing waits; try the documented offset form or reproduce the page’s required interaction.
Intermittent failures A fixed delay or race with asynchronous rendering. Replace sleeps with an explicit, outcome-based wait and assert the resulting state.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Timeout, polling, and test design

Choose a timeout that covers normal application latency in your test environment, then fail clearly when it is exceeded. A ten-second example is not a universal requirement. Keep waits local to the condition they protect rather than adding a large global delay. Explicit waits return as soon as the condition succeeds, so they are faster on normal runs and more tolerant of slower ones than fixed sleeps.

Keep the source and target lookup close to the action, and perform the post-drop lookup with a fresh locator. If a test must distinguish an old list from a new list, capture the old reference only for a staleness check and never treat it as a live model of the page.

Adapting the pattern to other Selenium bindings

The concepts are binding-independent: use the Actions API for the drag, the binding’s explicit-wait facility for presence or visibility, and a fresh locator for the resulting node. Java, JavaScript, C#, and Ruby expose equivalent Actions and wait APIs, but method names and expected-condition packages differ. Match the syntax to the Selenium version used by your project rather than copying Python method names into another binding.

Or skip the browser setup

If your goal is a clean image or PDF of a page state rather than an interaction test, ScreenshotNeo provides a one-call website screenshot API. It accepts a URL and can wait for a selector, a delay, or network idle; it can also click an element, run custom JavaScript, and capture a selected element. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets, with controls to disable each step.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for parameters and response details. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account to try it.

Frequently Asked Questions

Should I increase the wait timeout when a test fails once?

First confirm that the drop changes the expected state and that the locator matches the resulting DOM. Increase the timeout only when measured application latency legitimately exceeds the current limit.

Can I use a list index for the newly dropped item?

Only if ordering is an explicit, stable contract of the application. A durable item identifier scoped to the destination is safer when other users or asynchronous updates can change ordering.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What should I log when diagnosing a failed drop?

Record the source and destination locators, the destination state before and after the action, the wait condition that timed out, and the current DOM or relevant attributes. This separates interaction failures from locator and timing failures.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.