181 lines
6.4 KiB
Python
181 lines
6.4 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
Test #7: ContentEditable / Rich Text Editors
|
|
|
|
Symptom: browser_type() doesn't insert text
|
|
Root Cause: Element is contenteditable, not an <input> or <textarea>
|
|
Detection: element.contentEditable === 'true'
|
|
Fix: Focus via JavaScript, use execCommand('insertText') or Input.dispatchKeyEvent
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "tools" / "src"))
|
|
|
|
from gcu.browser.bridge import BeelineBridge
|
|
|
|
CONTEXT_NAME = "contenteditable-test"
|
|
|
|
|
|
async def test_contenteditable():
|
|
"""Test typing into contenteditable elements."""
|
|
print("=" * 70)
|
|
print("TEST #7: ContentEditable / Rich Text Editors")
|
|
print("=" * 70)
|
|
|
|
bridge = BeelineBridge()
|
|
|
|
try:
|
|
await bridge.start()
|
|
|
|
for i in range(10):
|
|
await asyncio.sleep(1)
|
|
if bridge.is_connected:
|
|
print("✓ Extension connected!")
|
|
break
|
|
else:
|
|
print("✗ Extension not connected")
|
|
return
|
|
|
|
context = await bridge.create_context(CONTEXT_NAME)
|
|
tab_id = context.get("tabId")
|
|
group_id = context.get("groupId")
|
|
print(f"✓ Created tab: {tab_id}")
|
|
|
|
# Create test page with contenteditable
|
|
test_html = """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>ContentEditable Test</title></head>
|
|
<body>
|
|
<h2>ContentEditable Test</h2>
|
|
|
|
<h3>1. Simple contenteditable div</h3>
|
|
<div id="editor1" contenteditable="true"
|
|
style="border:1px solid #ccc;padding:10px;
|
|
min-height:50px;">Start text</div>
|
|
|
|
<h3>2. Rich text editor (like Notion)</h3>
|
|
<div id="editor2" contenteditable="true"
|
|
style="border:1px solid #ccc;padding:10px;
|
|
min-height:50px;">
|
|
<p>Type here...</p>
|
|
</div>
|
|
|
|
<h3>3. Regular input (for comparison)</h3>
|
|
<input id="input1" type="text" placeholder="Regular input" />
|
|
|
|
<script>
|
|
// Track content changes
|
|
window.editor1Content = '';
|
|
window.editor2Content = '';
|
|
|
|
document.getElementById('editor1').addEventListener('input', (e) => {
|
|
window.editor1Content = e.target.innerText;
|
|
});
|
|
document.getElementById('editor2').addEventListener('input', (e) => {
|
|
window.editor2Content = e.target.innerText;
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Write to file and use file:// URL (data: URLs don't work well with extension)
|
|
test_file = Path("/tmp/contenteditable_test.html")
|
|
test_file.write_text(test_html.strip())
|
|
file_url = f"file://{test_file}"
|
|
await bridge.navigate(tab_id, file_url, wait_until="load")
|
|
print("✓ Page loaded")
|
|
|
|
# Screenshot with timeout protection
|
|
try:
|
|
screenshot = await asyncio.wait_for(bridge.screenshot(tab_id), timeout=10.0)
|
|
print(f"Screenshot: {len(screenshot.get('data', ''))} bytes")
|
|
except asyncio.TimeoutError:
|
|
print("Screenshot timed out (skipping)")
|
|
|
|
# Detect contenteditable
|
|
print("\n--- Detecting contenteditable elements ---")
|
|
detection = await bridge.evaluate(
|
|
tab_id,
|
|
"""
|
|
(function() {
|
|
const editables = document.querySelectorAll('[contenteditable="true"]');
|
|
return {
|
|
count: editables.length,
|
|
ids: Array.from(editables).map(el => el.id)
|
|
};
|
|
})();
|
|
""",
|
|
)
|
|
print(f"Contenteditable detection: {detection.get('result', {})}")
|
|
|
|
# Test 1: Type into regular input (baseline)
|
|
print("\n--- Test 1: Regular input ---")
|
|
await bridge.click(tab_id, "#input1")
|
|
await bridge.type_text(tab_id, "#input1", "Hello input")
|
|
input_result = await bridge.evaluate(
|
|
tab_id, "(function() { return document.getElementById('input1').value; })()"
|
|
)
|
|
print(f"Input value: {input_result.get('result', '')}")
|
|
|
|
# Test 2: Type into contenteditable div
|
|
print("\n--- Test 2: Contenteditable div ---")
|
|
await bridge.click(tab_id, "#editor1")
|
|
await bridge.type_text(tab_id, "#editor1", "Hello contenteditable", clear_first=True)
|
|
editor_result = await bridge.evaluate(
|
|
tab_id,
|
|
"(function() { return document.getElementById('editor1').innerText; })()",
|
|
)
|
|
print(f"Editor1 innerText: {editor_result.get('result', '')}")
|
|
|
|
# Test 3: Use JavaScript insertText for rich editor
|
|
print("\n--- Test 3: JavaScript insertText for rich editor ---")
|
|
insert_result = await bridge.evaluate(
|
|
tab_id,
|
|
"""
|
|
(function() {
|
|
const editor = document.getElementById('editor2');
|
|
editor.focus();
|
|
document.execCommand('selectAll', false, null);
|
|
document.execCommand('insertText', false, 'Hello from execCommand');
|
|
return editor.innerText;
|
|
})();
|
|
""",
|
|
)
|
|
print(f"Editor2 after execCommand: {insert_result.get('result', '')}")
|
|
|
|
# Screenshot after with timeout protection
|
|
try:
|
|
screenshot_after = await asyncio.wait_for(bridge.screenshot(tab_id), timeout=10.0)
|
|
print(f"Screenshot after: {len(screenshot_after.get('data', ''))} bytes")
|
|
except asyncio.TimeoutError:
|
|
print("Screenshot after timed out (skipping)")
|
|
|
|
# Results
|
|
print("\n--- Results ---")
|
|
input_val = input_result.get("result", "")
|
|
editor1_val = editor_result.get("result", "")
|
|
editor2_val = insert_result.get("result", "")
|
|
|
|
input_pass = "Hello input" in input_val
|
|
editor1_pass = "Hello contenteditable" in editor1_val
|
|
editor2_pass = "execCommand" in editor2_val
|
|
|
|
print(f"Input: {'✓ PASS' if input_pass else '✗ FAIL'} - {input_val}")
|
|
print(f"Editor1: {'✓ PASS' if editor1_pass else '✗ FAIL'} - {editor1_val}")
|
|
print(f"Editor2: {'✓ PASS' if editor2_pass else '✗ FAIL'} - {editor2_val}")
|
|
|
|
await bridge.destroy_context(group_id)
|
|
print("\n✓ Context destroyed")
|
|
|
|
finally:
|
|
await bridge.stop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(test_contenteditable())
|