Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsSome 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
- Drag: identify the source and destination and perform the action.
- Wait: wait for the resulting DOM or application state, not an arbitrary number of seconds.
- 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.
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.
#1 Best Overall
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.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallPresence 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.
Rank #2
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.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →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.
Rank #3
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.
Recommended Free Tools
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.
Rank #4
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.
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. |
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.
Best Value
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.
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.
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.
Quick Recap
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.

