Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions atest/acceptance/keywords/draganddropframe.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
*** Settings ***
Documentation Tests for the custom Drag And Drop To Frame keyword
... in cross-frame drag-and-drop scenarios.
Resource ../resource.robot
Test Setup Go To Page "frames/draganddrop.html"
Force Tags draganddrop

*** Test Cases ***
Drag And Drop To Frame Works With Local HTML
[Documentation] Verifies successful cross-frame drag-and-drop from default content to a target inside an iframe.
Wait Until Page Contains Element id=source timeout=10s
Drag And Drop To Frame id=source id=target id=previewFrame
Select Frame id=previewFrame
Element Should Contain id=target Dropped Successfully!
Unselect Frame

Drag And Drop To Frame Returns To Default Content
[Documentation] Verifies that the keyword returns to default content after execution.
Wait Until Page Contains Element id=source timeout=10s
Drag And Drop To Frame id=source id=target id=previewFrame
Element Should Be Visible id=previewFrame

Drag And Drop To Frame Hides Source Element
[Documentation] Verifies that the source element becomes hidden after a successful drop.
Wait Until Page Contains Element id=source timeout=10s
Drag And Drop To Frame id=source id=target id=previewFrame
Element Should Not Be Visible id=source

Standard Drag And Drop Fails When Target Is Inside Frame
[Documentation] Verifies that the standard Drag And Drop keyword cannot complete this cross-frame scenario.
Wait Until Page Contains Element id=source timeout=10s
Run Keyword And Expect Error * Drag And Drop id=source id=target
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not wildcard the error that is expected. This can cause the test to "pass" when any error occurs.

Select Frame id=previewFrame
Element Should Not Contain id=target Dropped Successfully!
Unselect Frame

Drag And Drop To Frame Fails With Invalid Frame
[Documentation] Verifies that the keyword fails when the frame locator is invalid.
Wait Until Page Contains Element id=source timeout=10s
Run Keyword And Expect Error * Drag And Drop To Frame
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not wildcard the error that is expected. This can cause the test to "pass" when any error occurs.
Element with locator 'id=missingFrame' not found

... id=source id=target id=missingFrame

Drag And Drop To Frame Fails With Invalid Target
[Documentation] Verifies that the keyword fails when the target element is not found inside the iframe.
Wait Until Page Contains Element id=source timeout=10s
Run Keyword And Expect Error * Drag And Drop To Frame
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not wildcard the error that is expected. This can cause the test to "pass" when any error occurs.
Element with locator 'id=missingTarget' not found

... id=source id=missingTarget id=previewFrame
95 changes: 95 additions & 0 deletions atest/resources/html/frames/draganddrop.html
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test fixture does not seem to work as intended.
Right now:
When clicking on the "Drag Me!" box, and performing a mouseup (releasing the mouse button anywhere) will trigger "Dropped Successfully", regardless of actually moving/draggint the box.

The test would pass even if move_to_element(target_element) did nothing.
The test would pass even if the frame switch silently failed (as long as a mouseup fires somewhere).
The test would pass if you replaced the whole keyword body with click_and_hold().release() on the source.

The fixture needs to validate that the drop actually lands on the target inside the frame, e.g. using real HTML5 DnD events or at least checking the mouseup coordinates hit the target's bounding box. As written, these tests give false confidence

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @yuriverweij, thanks for the detailed feedback. I agree with the suggestions and I’m working on the updates. I’ll push the changes shortly.

Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Mouse-Based Cross-Frame Drag Test (Fixed with Overlay)</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f0f0f0; position: relative; }
#source {
width: 150px; height: 150px; background: lightblue;
text-align: center; line-height: 150px; font-size: 20px;
cursor: pointer; user-select: none; position: absolute; top: 50px; left: 50px;
z-index: 10;
}
#iframe-container { position: relative; display: inline-block; margin-top: 250px; }
iframe { width: 800px; height: 500px; border: 3px solid #333; background: white; }
#overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: transparent; z-index: 20; display: none; cursor: default;
}
#target {
width: 400px; height: 300px; background: lightgreen;
margin: 100px auto; text-align: center; line-height: 300px; font-size: 28px;
border: 4px dashed #000;
}
</style>
</head>
<body>

<h2>Click & hold blue box (outside), drag ANYWHERE (including into green area inside iframe), release → drops!</h2>

<!-- Source outside iframe -->
<div id="source">Drag Me!<br>(outside iframe)</div>

<!-- Iframe wrapper with overlay -->
<div id="iframe-container">
<iframe id="previewFrame" srcdoc="
<html>
<head>
<style>
body { margin: 0; background: #fff; }
#target { width: 400px; height: 300px; background: lightgreen; margin: 100px auto; text-align: center; line-height: 300px; font-size: 28px; border: 4px dashed #000; }
</style>
<script>
window.addEventListener('message', function(e) {
if (e.data.action === 'drop') {
const target = document.getElementById('target');
const droppedBox = document.createElement('div');
droppedBox.innerHTML = 'Dropped Successfully!<br><br>Box from outside!';
droppedBox.style.cssText = 'width: 150px; height: 150px; background: lightblue; margin: 20px auto; line-height: 150px; font-size: 20px; text-align: center;';
target.innerHTML = '';
target.appendChild(droppedBox);
}
});
</script>
</head>
<body>
<div id='target'>Drop Here<br>(inside iframe)</div>
</body>
</html>
"></iframe>
<div id="overlay"></div>
</div>

<script>
let dragging = false;
const source = document.getElementById('source');
const overlay = document.getElementById('overlay');
const iframe = document.getElementById('previewFrame');

// Start drag: show overlay to capture events over iframe
source.addEventListener('mousedown', (e) => {
dragging = true;
overlay.style.display = 'block';
e.preventDefault();
});

// End drag: hide overlay, hide source, send drop to iframe
const endDrag = () => {
if (dragging) {
dragging = false;
overlay.style.display = 'none';
source.style.display = 'none';

iframe.contentWindow.postMessage({
action: 'drop'
}, '*');
}
};

// Mouseup anywhere in parent (including overlay)
document.addEventListener('mouseup', endDrag);
</script>

</body>
</html>
39 changes: 38 additions & 1 deletion src/SeleniumLibrary/keywords/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -1293,4 +1293,41 @@ def get_css_property_value(
| ${color}= | `Get CSS Property Value` | css:button.submit | background-color |
| ${size}= | `Get CSS Property Value` | id:username | font-size |
"""
return self.find_element(locator).value_of_css_property(css_property)
return self.find_element(locator).value_of_css_property(css_property)

@keyword('Drag And Drop To Frame')
def drag_and_drop_to_frame(
self, locator: Locator, target: Locator, frame: Locator,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to make the keyword more generic it might be worth changing some parts:

def drag_and_drop_across_frames( self, locator: Locator, target: Locator, target_frame: Locator, source_frame: Optional[Locator] = None,

  • locator: Locator of the source element to drag.
  • target: Locator of the drop target.
  • target_frame: Locator of the iframe containing the target.
  • source_frame: Locator of the iframe containing the source, or None if the source is in the default content.

) -> None:
"""
Drags the element identified by ``locator`` from default content and drops it onto
the ``target`` element inside the specified iframe.

The ``locator`` argument is the locator of the dragged element in default content,
the ``target`` is the locator of the drop target inside the iframe, and the
``frame`` is the locator of the iframe containing the target.

See the `Locating elements` section for details about the locator syntax.

This keyword is designed for cross-frame drag-and-drop scenarios where the standard
`Drag And Drop` keyword fails because it cannot switch contexts mid-action.

Example:
| Drag And Drop To Frame | css:div#draggable | css:div.drop-target | id:my-iframe |

Note: This assumes the source is in the default content and the target is inside
the iframe.
"""
source_element = self.find_element(locator)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.click_and_hold(source_element).perform()

try:
frame_element = self.find_element(frame)
self.driver.switch_to.frame(frame_element)
target_element = self.find_element(target)

action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.move_to_element(target_element).release().perform()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the find_element() for frame or target raises an error, the mouse button is still held down. The finally switches context back but never releases. The next action in the suite inherits a held mouse button.

finally:
self.driver.switch_to.default_content()
2 changes: 1 addition & 1 deletion utest/test/api/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def setUpClass(cls):
def test_no_libraries(self):
for item in [None, "None", ""]:
sl = SeleniumLibrary(plugins=item)
self.assertEqual(len(sl.get_keyword_names()), 183)
self.assertEqual(len(sl.get_keyword_names()), 184)

def test_parse_library(self):
plugin = "path.to.MyLibrary"
Expand Down
Loading