Update to itb_screenshot and friends

This commit is contained in:
2024-12-27 17:07:19 +01:00
parent fe72969b4f
commit e0bc7a7159
15 changed files with 6739 additions and 552 deletions

View File

@@ -2,22 +2,17 @@
import sys
import os
import time
import random
import argparse
import json
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.mouse_button import MouseButton
from selenium.webdriver.common.by import By
def debug(msg: str):
"""Print debug message to stderr"""
print(f"DEBUG: {msg}", file=sys.stderr)
class ClickExecutor:
def __init__(self, debug_port: int):
debug(f"Connecting to Chrome on debug port {debug_port}")
def __init__(self, debug_port: int, verbose: bool = False):
"""Initialize connection to Chrome instance"""
self.verbose = verbose
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debug_port}")
self.driver = webdriver.Chrome(options=options)
@@ -29,15 +24,15 @@ class ClickExecutor:
with open(os.path.join(js_dir, filename)) as f:
self.js_code += f.read() + "\n"
# Get initial window info
# Get window and viewport info
self.window_size = self.driver.get_window_size()
debug(f"Window size: {json.dumps(self.window_size)}")
self.viewport_size = self.get_viewport_size()
debug(f"Viewport size: {json.dumps(self.viewport_size)}")
self.scroll_position = self.get_scroll_position()
debug(f"Scroll position: {json.dumps(self.scroll_position)}")
def log(self, msg: str):
"""Log message if verbose mode is enabled"""
if self.verbose:
print(f"DEBUG: {msg}", file=sys.stderr)
def get_viewport_size(self):
"""Get viewport dimensions"""
@@ -58,37 +53,47 @@ class ClickExecutor:
max(0, x - self.viewport_size['width'] // 2),
max(0, y - self.viewport_size['height'] // 2)
)
# Wait for scroll to complete
time.sleep(0.5)
self.scroll_position = self.get_scroll_position()
debug(f"New scroll position: {json.dumps(self.scroll_position)}")
def get_element_at_position(self, x: int, y: int):
"""Get the element at the specified coordinates"""
return self.driver.execute_script(self.js_code + "; return getElementAtPosition(arguments[0], arguments[1]);", x, y)
return self.driver.execute_script(
self.js_code + "; return getElementAtPosition(arguments[0], arguments[1]);",
x, y
)
def execute_click(self, x: int, y: int, double: bool = False, right: bool = False):
"""Execute click operation with improved coordinate handling"""
def execute_click(self, x: int, y: int, double: bool = False, right: bool = False, element_id: str = None):
"""Execute click operation with improved handling"""
try:
debug(f"Attempting to click at coordinates ({x}, {y})")
debug(f"Click type: {'double' if double else 'right' if right else 'single'}")
self.log(f"Attempting {'double' if double else 'right' if right else 'single'} click at ({x}, {y})")
# First scroll to make target visible
self.scroll_to_target(x, y)
# Get the element at the target position
element = self.get_element_at_position(x, y)
if not element:
debug(f"No element found at coordinates ({x}, {y})")
return False
if element_id:
# Try to find element by ID first
try:
element = self.driver.find_element(By.ID, element_id)
self.log(f"Found element by ID: {element_id}")
except:
self.log(f"Failed to find element by ID: {element_id}")
return False
else:
# Scroll to make target visible
self.scroll_to_target(x, y)
# Try clicking the element directly
# Get element at position
element = self.get_element_at_position(x, y)
if not element:
self.log(f"No element found at coordinates ({x}, {y})")
return False
# Try clicking the element
try:
debug("Attempting to click element directly")
element = self.driver.execute_script("return arguments[0];", element)
actions = ActionChains(self.driver)
actions.move_to_element(element)
if element_id:
actions.move_to_element(element)
else:
actions.move_by_offset(x, y)
if right:
actions.context_click()
@@ -101,28 +106,29 @@ class ClickExecutor:
return True
except Exception as e:
debug(f"Direct element click failed: {str(e)}")
debug("Falling back to JavaScript click")
self.log(f"Direct click failed: {str(e)}")
self.log("Attempting JavaScript click fallback")
# Fallback to JavaScript click
try:
self.driver.execute_script("arguments[0].click();", element)
return True
except Exception as js_error:
debug(f"JavaScript click failed: {str(js_error)}")
self.log(f"JavaScript click failed: {str(js_error)}")
return False
except Exception as e:
debug(f"Click operation failed with error: {str(e)}")
self.log(f"Click operation failed: {str(e)}")
return False
def main():
parser = argparse.ArgumentParser(description='Execute mouse clicks in Chrome')
parser.add_argument('debug_port', type=int, help='Chrome debug port')
parser.add_argument('-x', type=int, required=True, help='X coordinate')
parser.add_argument('-y', type=int, required=True, help='Y coordinate')
parser.add_argument('-x', type=int, help='X coordinate')
parser.add_argument('-y', type=int, help='Y coordinate')
parser.add_argument('--double', action='store_true', help='Perform double click')
parser.add_argument('--right', action='store_true', help='Perform right click')
parser.add_argument('--id', help='Element ID to click')
parser.add_argument('-v', '--verbose', action='store_true', help='Enable debug output')
args = parser.parse_args()
@@ -130,15 +136,25 @@ def main():
print("Cannot specify both --double and --right", file=sys.stderr)
sys.exit(1)
if not args.id and (args.x is None or args.y is None):
print("Must specify either element ID or both x and y coordinates", file=sys.stderr)
sys.exit(1)
try:
debug("Initializing ClickExecutor")
executor = ClickExecutor(args.debug_port)
success = executor.execute_click(args.x, args.y, args.double, args.right)
executor = ClickExecutor(args.debug_port, args.verbose)
success = executor.execute_click(
x=args.x if args.x is not None else 0,
y=args.y if args.y is not None else 0,
double=args.double,
right=args.right,
element_id=args.id
)
sys.exit(0 if success else 1)
except Exception as e:
debug(f"Fatal error: {str(e)}")
if args.verbose:
print(f"Fatal error: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
main()

View File

@@ -0,0 +1,168 @@
#!/usr/bin/env python3
import sys
import os
import time
import argparse
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
class InputController:
def __init__(self, debug_port: int, verbose: bool = False):
"""Initialize connection to Chrome instance"""
self.verbose = verbose
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debug_port}")
self.driver = webdriver.Chrome(options=options)
def log(self, msg: str):
"""Log message if verbose mode is enabled"""
if self.verbose:
print(f"DEBUG: {msg}", file=sys.stderr)
def focus_element(self, element_id: str = None):
"""Focus on an input element"""
try:
if element_id:
element = self.driver.find_element(By.ID, element_id)
else:
# Get currently focused element
element = self.driver.switch_to.active_element
element.click()
return element
except Exception as e:
self.log(f"Failed to focus element: {str(e)}")
return None
def enter_text(self, text: str, element_id: str = None, append: bool = False):
"""Enter text into an input element"""
try:
element = self.focus_element(element_id)
if not element:
return False
if not append:
# Clear existing text first
if element.tag_name.lower() in ['input', 'textarea']:
element.clear()
else:
# For contenteditable elements
element.send_keys(Keys.CONTROL + 'a')
element.send_keys(Keys.DELETE)
# Type the text
element.send_keys(text)
return True
except Exception as e:
self.log(f"Failed to enter text: {str(e)}")
return False
def set_caret_position(self, position: int, element_id: str = None):
"""Set the text cursor position"""
try:
element = self.focus_element(element_id)
if not element:
return False
# Use JavaScript to set caret position
self.driver.execute_script(
"""
const el = arguments[0];
const pos = arguments[1];
if (el.setSelectionRange) {
el.setSelectionRange(pos, pos);
} else if (el.createTextRange) {
const range = el.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
""",
element, position
)
return True
except Exception as e:
self.log(f"Failed to set caret position: {str(e)}")
return False
def select_text(self, start: int, end: int, element_id: str = None):
"""Select text range in an input element"""
try:
element = self.focus_element(element_id)
if not element:
return False
# Use JavaScript to set selection
self.driver.execute_script(
"""
const el = arguments[0];
const start = arguments[1];
const end = arguments[2];
if (el.setSelectionRange) {
el.setSelectionRange(start, end);
} else if (el.createTextRange) {
const range = el.createTextRange();
range.collapse(true);
range.moveStart('character', start);
range.moveEnd('character', end);
range.select();
}
""",
element, start, end
)
return True
except Exception as e:
self.log(f"Failed to select text: {str(e)}")
return False
def main():
parser = argparse.ArgumentParser(description='Control text input in Chrome')
parser.add_argument('debug_port', type=int, help='Chrome debug port')
parser.add_argument('-i', '--id', help='Element ID')
parser.add_argument('-t', '--text', help='Text to input')
parser.add_argument('--append', action='store_true', help='Append text instead of replacing')
parser.add_argument('--caret', type=int, help='Set caret position')
parser.add_argument('--select-start', type=int, help='Text selection start')
parser.add_argument('--select-end', type=int, help='Text selection end')
parser.add_argument('-v', '--verbose', action='store_true', help='Enable debug output')
args = parser.parse_args()
# Validate args
if args.select_start is not None and args.select_end is None:
print("Must specify both --select-start and --select-end", file=sys.stderr)
sys.exit(1)
if args.select_end is not None and args.select_start is None:
print("Must specify both --select-start and --select-end", file=sys.stderr)
sys.exit(1)
try:
controller = InputController(args.debug_port, args.verbose)
success = False
if args.text is not None:
success = controller.enter_text(args.text, args.id, args.append)
elif args.caret is not None:
success = controller.set_caret_position(args.caret, args.id)
elif args.select_start is not None:
success = controller.select_text(args.select_start, args.select_end, args.id)
else:
success = controller.focus_element(args.id) is not None
sys.exit(0 if success else 1)
except Exception as e:
if args.verbose:
print(f"Fatal error: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import sys
import os
import time
import argparse
from selenium import webdriver
class NavigationController:
def __init__(self, debug_port: int):
"""Initialize connection to Chrome instance"""
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debug_port}")
self.driver = webdriver.Chrome(options=options)
# Load JavaScript files
js_dir = os.path.join(os.path.dirname(__file__), "..", "js")
self.js_code = ""
with open(os.path.join(js_dir, "viewport.js")) as f:
self.js_code = f.read()
def navigate(self, url: str) -> bool:
"""
Navigate to the specified URL
Returns True if navigation was successful
"""
try:
# Store initial URL for comparison
initial_url = self.driver.current_url
# Execute navigation
self.driver.get(url)
# Wait for navigation to complete
max_wait = 10 # seconds
start_time = time.time()
while time.time() - start_time < max_wait:
# Check if URL has changed
if self.driver.current_url != initial_url:
# Wait a bit more for page to stabilize
time.sleep(0.5)
return True
time.sleep(0.1)
# If we get here, navigation may have failed
print(f"Warning: Navigation timeout after {max_wait} seconds", file=sys.stderr)
return False
except Exception as e:
print(f"Error during navigation: {str(e)}", file=sys.stderr)
return False
def main():
parser = argparse.ArgumentParser(description='Navigate to URL in Chrome')
parser.add_argument('debug_port', type=int, help='Chrome debug port')
parser.add_argument('url', help='URL to navigate to')
args = parser.parse_args()
try:
controller = NavigationController(args.debug_port)
success = controller.navigate(args.url)
sys.exit(0 if success else 1)
except Exception as e:
print(f"Fatal error: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,162 @@
#!/usr/bin/env python3
import sys
import os
import time
import argparse
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class RefreshController:
def __init__(self, debug_port: int, verbose: bool = False):
"""Initialize connection to Chrome instance"""
self.verbose = verbose
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debug_port}")
self.driver = webdriver.Chrome(options=options)
# Load JavaScript files
js_dir = os.path.join(os.path.dirname(__file__), "..", "js")
self.js_code = ""
with open(os.path.join(js_dir, "viewport.js")) as f:
self.js_code = f.read()
def log(self, msg: str):
"""Log message if verbose mode is enabled"""
if self.verbose:
print(f"DEBUG: {msg}", file=sys.stderr)
def get_page_state(self) -> dict:
"""Get current page state for comparison"""
return {
'url': self.driver.current_url,
'title': self.driver.title,
'scroll_position': self.driver.execute_script(
self.js_code + "; return getViewportInfo();"
)
}
def wait_for_page_change(self, original_state: dict, timeout: int = 30) -> bool:
"""Wait for page to refresh and stabilize"""
try:
start_time = time.time()
while time.time() - start_time < timeout:
current_state = self.get_page_state()
# Check if page has reloaded
if (current_state['url'] == original_state['url'] and
current_state['title'] == original_state['title']):
# Check if DOM has finished updating
is_stable = self.driver.execute_script("""
return (typeof window._lastMutation === 'undefined' ||
Date.now() - window._lastMutation > 1000);
""")
if is_stable:
return True
time.sleep(0.1)
self.log("Timeout waiting for page to stabilize")
return False
except Exception as e:
self.log(f"Error waiting for page change: {str(e)}")
return False
def setup_mutation_tracking(self):
"""Setup mutation observer to track DOM changes"""
self.driver.execute_script("""
// Track last mutation time
window._lastMutation = Date.now();
// Create mutation observer
const observer = new MutationObserver(() => {
window._lastMutation = Date.now();
});
// Observe entire document for any changes
observer.observe(document, {
childList: true,
subtree: true,
attributes: true,
characterData: true
});
""")
def refresh(self, hard: bool = False, timeout: int = 30) -> bool:
"""
Refresh the current page
Args:
hard (bool): If True, bypass cache with Ctrl+F5
timeout (int): Maximum time to wait for refresh in seconds
Returns:
bool: True if refresh was successful
"""
try:
# Get initial page state
original_state = self.get_page_state()
self.log(f"Original page state: {original_state}")
# Setup mutation tracking
self.setup_mutation_tracking()
if hard:
# Force reload bypassing cache
self.log("Executing hard refresh (Ctrl+F5)")
self.driver.execute_script("""
window.location.reload(true);
""")
else:
# Normal reload
self.log("Executing normal refresh")
self.driver.refresh()
# Wait for page to stabilize
success = self.wait_for_page_change(original_state, timeout)
if success:
# Restore scroll position
self.driver.execute_script(
"window.scrollTo(arguments[0], arguments[1]);",
original_state['scroll_position']['scroll_x'],
original_state['scroll_position']['scroll_y']
)
# Final verification
new_state = self.get_page_state()
self.log(f"New page state: {new_state}")
return True
return False
except Exception as e:
self.log(f"Refresh failed: {str(e)}")
return False
def main():
parser = argparse.ArgumentParser(description='Refresh page in Chrome')
parser.add_argument('debug_port', type=int, help='Chrome debug port')
parser.add_argument('--hard', action='store_true', help='Force refresh bypassing cache')
parser.add_argument('--timeout', type=int, default=30, help='Refresh timeout in seconds')
parser.add_argument('-v', '--verbose', action='store_true', help='Enable debug output')
args = parser.parse_args()
try:
controller = RefreshController(args.debug_port, args.verbose)
success = controller.refresh(args.hard, args.timeout)
sys.exit(0 if success else 1)
except Exception as e:
if args.verbose:
print(f"Fatal error: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,12 +1,15 @@
#!/usr/bin/env python3
import sys
import os
import argparse
from selenium import webdriver
from selenium.webdriver.common.by import By
import xml.etree.ElementTree as ET
class ScreenshotGenerator:
def __init__(self, debug_port: int):
def __init__(self, debug_port: int, debug: bool = False):
"""Initialize the screenshot generator with optional debug mode"""
self.debug = debug
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debug_port}")
self.driver = webdriver.Chrome(options=options)
@@ -18,69 +21,294 @@ class ScreenshotGenerator:
with open(os.path.join(js_dir, filename)) as f:
self.js_code += f.read() + "\n"
def get_page_content(self) -> dict:
return self.driver.execute_script(self.js_code + "; return analyzePageContent();")
def log(self, msg: str):
"""Log message if verbose mode is enabled"""
if self.debug:
print(f"[Debug] {msg}")
def format_xml(self, xml_str: str) -> str:
root = ET.fromstring(xml_str)
ET.indent(ET.ElementTree(root), space=" ", level=0)
return ET.tostring(root, encoding='unicode', method='xml')
def get_visible_dom(self) -> str:
"""Get only the visible DOM elements with enhanced visibility detection"""
if self.debug:
self.log("Starting DOM analysis")
print("Debug mode enabled, gathering DOM information...")
try:
script = """
// Debug helper
const debug = {
log: [],
add: function(msg) {
this.log.push(msg);
}
};
function isVisible(element) {
debug.add(`Checking visibility for ${element.tagName}`);
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
debug.add('Not an element node');
return false;
}
const style = window.getComputedStyle(element);
const rect = element.getBoundingClientRect();
debug.add(`Element: ${element.tagName}, display: ${style.display}, visibility: ${style.visibility}`);
debug.add(`Dimensions: ${rect.width}x${rect.height}`);
return (
style.display !== 'none' &&
style.visibility !== 'hidden' &&
rect.width > 0 &&
rect.height > 0
);
}
function getElementInfo(element) {
const info = {
tag: element.tagName.toLowerCase(),
attributes: {},
text: element.textContent.trim()
};
['id', 'class', 'href', 'src'].forEach(attr => {
if (element.hasAttribute(attr)) {
info.attributes[attr] = element.getAttribute(attr);
}
});
return info;
}
function traverseDOM(element, depth = 0) {
if (!element) {
debug.add('Null element encountered');
return '';
}
let output = '';
if (element.nodeType === Node.ELEMENT_NODE && isVisible(element)) {
const info = getElementInfo(element);
const indent = ' '.repeat(depth);
let elementStr = `${indent}${info.tag}`;
Object.entries(info.attributes).forEach(([key, value]) => {
elementStr += ` ${key}="${value}"`;
});
if (info.text && !element.children.length) {
elementStr += ` [text: ${info.text}]`;
}
output += elementStr + '\\n';
Array.from(element.children).forEach(child => {
output += traverseDOM(child, depth + 1);
});
}
return output;
}
const dom = traverseDOM(document.documentElement);
debug.add(`Total output length: ${dom.length}`);
return {
result: dom,
debug: debug.log,
stats: {
url: window.location.href,
title: document.title,
elementCount: document.getElementsByTagName('*').length,
bodyChildCount: document.body ? document.body.children.length : 0
}
};
"""
result = self.driver.execute_script(script)
if isinstance(result, dict):
if self.debug:
print("\n=== Page Statistics ===")
stats = result.get('stats', {})
print(f"Title: {stats.get('title')}")
print(f"Element count: {stats.get('elementCount')}")
print(f"Body children: {stats.get('bodyChildCount')}")
print("\n=== Debug Log ===")
for log_entry in result.get('debug', []):
print(f"Browser: {log_entry}")
return result.get('result', '')
return result
except Exception as e:
if self.debug:
print(f"Error in get_visible_dom: {str(e)}")
return ""
def get_full_dom(self) -> str:
"""Get the full DOM structure"""
return self.driver.execute_script("""
function traverseDOM(node, indent = '') {
if (!node) return '';
let result = '';
// Add node information
if (node.nodeType === Node.ELEMENT_NODE) {
result += indent + node.tagName.toLowerCase();
if (node.id) result += ` id="${node.id}"`;
if (node.className) result += ` class="${node.className}"`;
result += '\\n';
// Process children
for (let child of node.children) {
result += traverseDOM(child, indent + ' ');
}
}
return result;
}
return traverseDOM(document.documentElement);
""")
def generate_xml(self) -> str:
page_data = self.get_page_content()
page_data = self.driver.execute_script(self.js_code + "; return analyzePageContent();")
# Create viewport element with attributes
viewport = ET.Element("viewport")
for k, v in page_data['viewport'].items():
viewport.set(k, str(v))
for key, value in page_data['viewport'].items():
viewport.set(key, str(value))
def process_node(node, parent):
if node is None:
return
if node['text']:
if len(parent) == 0:
parent.text = (parent.text or '') + node['text'] + '\n'
def process_element(element_data, parent):
# Add fixed position indication if present
if element_data.get('fixed'):
if element_data.get('tag') == 'nav':
nav = ET.SubElement(parent, "navigation")
nav.set("fixed", "true")
nav.set("x", str(element_data['x']))
nav.set("y", str(element_data['y']))
nav.set("width", str(element_data['width']))
nav.set("height", str(element_data['height']))
current_parent = nav
else:
parent[-1].tail = (parent[-1].tail or '') + node['text'] + '\n'
if node['interactions']:
interactive = ET.SubElement(parent, "interactive")
interactive.set("x", str(node['location']['x']))
interactive.set("y", str(node['location']['y']))
interactive.set("width", str(node['size']['width']))
interactive.set("height", str(node['size']['height']))
interactive.set("interactions", ",".join(
i for i in node['interactions'] if not i.startswith('placeholder:')
))
fixed_container = ET.SubElement(parent, "fixed")
fixed_container.set("x", str(element_data['x']))
fixed_container.set("y", str(element_data['y']))
fixed_container.set("width", str(element_data['width']))
fixed_container.set("height", str(element_data['height']))
current_parent = fixed_container
else:
current_parent = parent
# Handle text content
if element_data.get('text'):
if len(current_parent) == 0:
current_parent.text = (current_parent.text or '') + element_data['text'] + '\n'
else:
current_parent[-1].tail = (current_parent[-1].tail or '') + element_data['text'] + '\n'
# Handle interactive elements
if element_data.get('interactions'):
interactive = ET.SubElement(current_parent, "interactive")
interactive.set("x", str(element_data['x']))
interactive.set("y", str(element_data['y']))
interactive.set("width", str(element_data['width']))
interactive.set("height", str(element_data['height']))
interactive.set("interactions", ','.join(element_data['interactions']))
placeholder = next(
(i.split(':', 1)[1] for i in node['interactions']
if i.startswith('placeholder:')),
None
)
if placeholder:
interactive.set('placeholder', placeholder)
if node['text']:
interactive.text = node['text']
for child in node['children']:
process_node(child, parent)
if element_data.get('id'):
interactive.set("id", element_data['id'])
elif element_data.get('suggestedClick'):
interactive.set("suggested_click",
f"{element_data['suggestedClick']['x']},{element_data['suggestedClick']['y']}")
if element_data.get('placeholder'):
interactive.set("placeholder", element_data['placeholder'])
if element_data.get('buttonText'):
interactive.text = element_data['buttonText']
# Handle images
if element_data.get('tag') == 'img':
img = ET.SubElement(current_parent, "img")
img.set("x", str(element_data['x']))
img.set("y", str(element_data['y']))
img.set("width", str(element_data['width']))
img.set("height", str(element_data['height']))
img.set("src", element_data.get('src', ''))
img.set("alt", element_data.get('alt', ''))
if element_data.get('id'):
img.set("id", element_data['id'])
# Process children
for child in element_data.get('children', []):
process_element(child, current_parent)
# Process all elements
process_element(page_data['content'], viewport)
process_node(page_data['content'], viewport)
return self.format_xml(ET.tostring(viewport, encoding='unicode'))
def main(debug_port: int):
def format_xml(self, xml_str: str) -> str:
"""Format XML with proper indentation"""
root = ET.fromstring(xml_str)
ET.indent(root, space=" ", level=0)
return ET.tostring(root, encoding='unicode', method='xml')
def main():
parser = argparse.ArgumentParser(description='Generate page screenshot in XML format')
parser.add_argument('debug_port', type=int, help='Chrome debug port')
parser.add_argument('--debug', action='store_true', help='Enable debug output')
parser.add_argument('--dom', action='store_true', help='Print full DOM structure')
parser.add_argument('--visible-dom', action='store_true', help='Print visible DOM structure')
args = parser.parse_args()
try:
generator = ScreenshotGenerator(debug_port)
xml_output = generator.generate_xml()
print(xml_output)
generator = ScreenshotGenerator(args.debug_port, args.debug)
# Basic DOM check when debug is enabled
if args.debug:
print("\nPerforming basic DOM check...")
basic_check = generator.driver.execute_script("""
return {
title: document.title,
bodyExists: !!document.body,
elementCount: document.getElementsByTagName('*').length,
firstElement: document.body ? document.body.firstElementChild.tagName : 'none'
}
""")
print(f"Page title: {basic_check['title']}")
print(f"Body exists: {basic_check['bodyExists']}")
print(f"Total elements: {basic_check['elementCount']}")
print(f"First element: {basic_check['firstElement']}")
if args.dom:
print("=== Full DOM Structure ===")
try:
dom_output = generator.get_full_dom()
if dom_output.strip():
print(dom_output)
else:
print("No DOM content found.")
except Exception as e:
print(f"Error retrieving DOM structure: {str(e)}")
elif args.visible_dom:
print("=== Visible DOM Structure ===")
try:
visible_dom = generator.get_visible_dom()
if visible_dom.strip():
print(visible_dom)
else:
print("No visible DOM content found.")
except Exception as e:
print(f"Error retrieving visible DOM structure: {str(e)}")
else:
xml_output = generator.generate_xml()
print(xml_output)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: itb_screenshot <debug_port>")
sys.exit(1)
main(int(sys.argv[1]))
main()

View File

@@ -1,267 +0,0 @@
# Comprehensive ITB Social Media Interaction Example
## Overview
This example demonstrates how an AI agent uses ITB to interact with a social media interface. It showcases ITB's key features including text collapsing, state management, and complex interactions. The scenario walks through a complete social media interaction flow, from content creation to user engagement.
## User Request to SIA
"I want you to help me create a social media post about my morning hike. I have a photo at '/photos/sunrise.jpg' to share. First, search for similar hiking posts to see what hashtags are popular. Then create a new post with my photo and some engaging text. After posting, find and interact with other hiking-related posts - like them and leave encouraging comments. Finally, send a chat message to anyone who's online and interested in hiking."
## SIA's Response and Actions
### Step 1: Initialize Session
```xml
<reasoning>
First, I'll start an ITB session and establish continuous monitoring of the page state.
</reasoning>
<single>
itb_start "social.html"
</single>
```
Expected Output:
```json
{
"pid": 12345,
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "success"
}
```
```xml
<repeat>
itb_screenshot 550e8400-e29b-41d4-a716-446655440000 --wait_stable
</repeat>
```
Initial viewport state:
```xml
<viewport width="1024" height="800" scroll_x="0" scroll_y="0">
<!-- Navigation bar with collapsed text -->
<text x="20" y="30">SocialConnect | Search posts... | Messages | Notifications</text>
<!-- Interactive navigation elements -->
<interactive accepts_text="true" x="200" y="30" width="300" height="30"
placeholder="Search posts..." aria-label="Search posts"/>
<interactive clickable="true" id="profile_menu" x="720" y="30" width="30" height="30">
<img src="https://picsum.photos/100" alt="Your profile picture"/>
</interactive>
<!-- Post creation area -->
<interactive accepts_text="true" x="340" y="100" width="400" height="100"
placeholder="What's on your mind?" aria-label="Create a post"/>
<interactive accepts_file="true" id="post_images" x="340" y="130" width="200" height="30"
multiple="true" accept="image/*"/>
<interactive clickable="true" x="340" y="160" width="80" height="30">Post</interactive>
<!-- Feed posts with collapsed text content -->
<text x="100" y="220">Alice Johnson - 2 hours ago - Just finished my morning hike! The sunrise was absolutely breathtaking today. 🌄 #MorningHike #Nature #Wellness</text>
<img src="https://picsum.photos/800/600?random=2" alt="Sunrise view from mountain top" x="100" y="320" width="600" height="450"/>
<!-- Interactive post actions -->
<interactive clickable="true" x="340" y="520" width="80" height="30">Like</interactive>
<interactive clickable="true" x="340" y="560" width="80" height="30">Comment</interactive>
<interactive clickable="true" x="340" y="600" width="80" height="30">Share</interactive>
<!-- Sidebar with collapsed text -->
<text x="960" y="30">Online Friends</text>
<interactive clickable="true" x="960" y="200" width="200" height="40">Emma Davis (Online)</interactive>
</viewport>
```
### Step 2: Search for Hiking Posts
```xml
<reasoning>
I see the search box in the navigation bar. I'll click it and search for hiking-related posts to analyze common hashtags.
</reasoning>
<single>
itb_cursor 550e8400-e29b-41d4-a716-446655440000 -x 250 -y 30 --click
</single>
<single>
itb_input 550e8400-e29b-41d4-a716-446655440000 -t "#hiking"
</single>
```
Updated viewport showing search results:
```xml
<viewport width="1024" height="800" scroll_x="0" scroll_y="0">
<!-- Navigation and search UI remain unchanged -->
<!-- Search results header -->
<text x="100" y="80">Search results for "#hiking" - 3 posts found</text>
<!-- Collapsed search result posts -->
<text x="100" y="120">Alice Johnson - 2 hours ago - Just finished my morning hike! The sunrise was absolutely breathtaking today. 🌄 #MorningHike #Nature #Wellness</text>
<img src="https://picsum.photos/800/600?random=2" alt="Sunrise view from mountain top" x="100" y="220" width="600" height="450"/>
<text x="100" y="720">David Brown - 1 day ago - Weekend hiking adventure! Found this hidden waterfall trail. #Hiking #Adventure #NaturePhotography</text>
<img src="https://picsum.photos/800/600?random=3" alt="Waterfall on hiking trail" x="100" y="820" width="600" height="450"/>
</viewport>
```
### Step 3: Create New Post
```xml
<reasoning>
Having analyzed the existing posts and popular hashtags, I'll now create our new post with the sunrise photo.
</reasoning>
<single>
itb_scroll 550e8400-e29b-41d4-a716-446655440000 -x 0 -y 0
itb_cursor 550e8400-e29b-41d4-a716-446655440000 -x 340 -y 100 --click
</single>
<single>
itb_input 550e8400-e29b-41d4-a716-446655440000 -t "Starting my day with an invigorating mountain hike! The colors of dawn painted the sky in stunning shades of orange and purple. Nothing beats the serenity of nature in the early morning. 🌄 #MorningHike #Nature #Wellness #SunriseViews"
</single>
<single>
itb_upload 550e8400-e29b-41d4-a716-446655440000 -i "post_images" -f "/photos/sunrise.jpg"
</single>
```
Viewport showing post creation state:
```xml
<viewport width="1024" height="800" scroll_x="0" scroll_y="0">
<!-- Navigation and header remain unchanged -->
<!-- Post creation area with entered content -->
<text x="340" y="100">Starting my day with an invigorating mountain hike! The colors of dawn painted the sky in stunning shades of orange and purple. Nothing beats the serenity of nature in the early morning. 🌄 #MorningHike #Nature #Wellness #SunriseViews</text>
<img src="file:///photos/sunrise.jpg" alt="Uploaded sunrise photo" x="340" y="220" width="400" height="300"/>
<interactive clickable="true" x="720" y="220" width="20" height="20" aria-label="Remove uploaded photo">×</interactive>
</viewport>
```
```xml
<single>
itb_cursor 550e8400-e29b-41d4-a716-446655440000 -x 340 -y 160 --click
</single>
```
Viewport after post creation:
```xml
<viewport width="1024" height="800" scroll_x="0" scroll_y="0">
<!-- Navigation remains unchanged -->
<!-- Newly created post appears at top of feed -->
<text x="100" y="120">You - Just now - Starting my day with an invigorating mountain hike! The colors of dawn painted the sky in stunning shades of orange and purple. Nothing beats the serenity of nature in the early morning. 🌄 #MorningHike #Nature #Wellness #SunriseViews</text>
<img src="file:///photos/sunrise.jpg" alt="Uploaded sunrise photo" x="100" y="220" width="600" height="450"/>
</viewport>
```
### Step 4: Interact with Related Posts
```xml
<reasoning>
Now I'll scroll to find Alice's hiking post and engage with it through likes and comments.
</reasoning>
<single>
itb_scroll 550e8400-e29b-41d4-a716-446655440000 --to-element "post-actions"
</single>
<single>
itb_cursor 550e8400-e29b-41d4-a716-446655440000 -x 340 -y 520 --click
</single>
```
Viewport after liking:
```xml
<viewport width="1024" height="800" scroll_x="0" scroll_y="600">
<text x="100" y="120">Alice Johnson - 2 hours ago - Just finished my morning hike! The sunrise was absolutely breathtaking today. 🌄 #MorningHike #Nature #Wellness</text>
<img src="https://picsum.photos/800/600?random=2" alt="Sunrise view from mountain top" x="100" y="220" width="600" height="450"/>
<interactive clickable="true" x="340" y="520" width="80" height="30" state="active">Like</interactive>
</viewport>
```
```xml
<single>
itb_cursor 550e8400-e29b-41d4-a716-446655440000 -x 340 -y 580 --click
itb_input 550e8400-e29b-41d4-a716-446655440000 -t "What a gorgeous view! I just did a morning hike too. The sunrise is always worth the early wake-up call! 🌅"
</single>
```
### Step 5: Start a Chat
```xml
<reasoning>
I notice Emma Davis is online and has hiking photos. I'll initiate a conversation about hiking.
</reasoning>
<single>
itb_cursor 550e8400-e29b-41d4-a716-446655440000 -x 960 -y 200 --click
</single>
```
Viewport showing chat window:
```xml
<viewport width="1024" height="800" scroll_x="0" scroll_y="600">
<!-- Main content remains with added chat widget -->
<interactive class="chat-widget" x="744" y="400" width="280" height="400">
<text x="754" y="420">Emma Davis (Online)</text>
<interactive accepts_text="true" x="754" y="760" width="260" height="30"
placeholder="Type a message..." aria-label="Type a message"/>
</interactive>
</viewport>
```
```xml
<single>
itb_input 550e8400-e29b-41d4-a716-446655440000 -t "Hi Emma! I noticed you're into hiking too. Would you be interested in joining for a sunrise hike sometime? I just discovered some beautiful trails!"
</single>
```
Final viewport state:
```xml
<viewport width="1024" height="800" scroll_x="0" scroll_y="600">
<!-- Chat widget with sent message -->
<interactive class="chat-widget" x="744" y="400" width="280" height="400">
<text x="754" y="420">Emma Davis (Online)</text>
<text x="754" y="480">You - Just now - Hi Emma! I noticed you're into hiking too. Would you be interested in joining for a sunrise hike sometime? I just discovered some beautiful trails!</text>
<interactive accepts_text="true" x="754" y="760" width="260" height="30"
placeholder="Type a message..." aria-label="Type a message"/>
</interactive>
</viewport>
```
```xml
<reasoning>
I've completed all requested interactions:
1. Analyzed existing hiking posts and hashtags
2. Created a new post with photo and appropriate hashtags
3. Engaged with Alice's hiking post through a like and comment
4. Initiated a conversation with Emma about hiking
The continuous screenshot monitoring remains active to catch any responses.
</reasoning>
```
## Key Features Demonstrated
This example illustrates several important ITB features:
1. Text Collapsing: Related text elements are automatically combined into natural reading flows, such as "Author - Time - Content" for posts.
2. State Management: The viewport maintains awareness of scroll position, focus state, and interactive element states throughout the session.
3. Interactive Elements: The example shows how ITB handles various types of interaction including:
- Text input fields
- File uploads
- Clickable buttons
- Scrollable areas
- Modal windows (chat widget)
4. Media Handling: Images are represented with their position, size, and alt text while maintaining proper layout relationships with surrounding content.
5. Dynamic Content: The example demonstrates how ITB handles dynamic content updates like:
- Search results loading
- Post creation
- Chat window opening
- Comment threading
This comprehensive example shows how ITB transforms complex web interfaces into a format that AI agents can effectively process and interact with while maintaining all the functionality a human user would have.

View File

@@ -1,67 +1,208 @@
function processElement(element) {
const rect = element.getBoundingClientRect();
if (!element.offsetParent && element !== document.body) return null;
if (rect.width === 0 || rect.height === 0) return null;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
if (rect.bottom < 0 || rect.top > viewportHeight ||
rect.right < 0 || rect.left > viewportWidth) {
return null;
// Helper function to check if element is clickable
function isClickable(element) {
// Check tag name
if (element.tagName === 'BUTTON' ||
element.tagName === 'A' ||
(element.tagName === 'INPUT' && element.type === 'submit')) {
return true;
}
const styles = window.getComputedStyle(element);
// Check ARIA role
if (element.getAttribute('role') === 'button') {
return true;
}
const text = Array.from(element.childNodes)
.filter(node => node.nodeType === Node.TEXT_NODE)
.map(node => node.textContent.trim())
.join(' ');
const interactions = [];
if (element.tagName === 'BUTTON' || element.tagName === 'A' ||
element.getAttribute('role') === 'button' ||
// Check for click handlers
if (element.onclick ||
element.getAttribute('onclick') ||
styles.cursor === 'pointer') {
interactions.push('click');
element.getAttribute('ng-click') ||
element.getAttribute('v-on:click')) {
return true;
}
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' ||
element.getAttribute('contenteditable') === 'true') {
interactions.push('text_input');
const placeholder = element.getAttribute('placeholder');
if (placeholder) interactions.push('placeholder:' + placeholder);
// Check computed style
const style = window.getComputedStyle(element);
if (style.cursor === 'pointer') {
return true;
}
if (element.tagName === 'SELECT' ||
element.getAttribute('role') === 'listbox' ||
element.getAttribute('type') === 'checkbox' ||
element.getAttribute('type') === 'radio') {
interactions.push('select');
return false;
}
// Helper function to check if element is an input
function isInputElement(element) {
const inputTags = ['INPUT', 'TEXTAREA', 'SELECT'];
if (inputTags.includes(element.tagName)) {
return true;
}
if (element.scrollHeight > element.clientHeight ||
element.scrollWidth > element.clientWidth) {
interactions.push('scroll');
if (element.getAttribute('contenteditable') === 'true') {
return true;
}
const children = Array.from(element.children)
.map(child => processElement(child))
.filter(child => child !== null);
if (element.getAttribute('role') === 'textbox' ||
element.getAttribute('role') === 'combobox') {
return true;
}
return false;
}
// Helper function to check if element is scrollable
function isScrollable(element) {
const style = window.getComputedStyle(element);
// Check for scroll overflow
if (style.overflow === 'auto' ||
style.overflow === 'scroll' ||
style.overflowY === 'auto' ||
style.overflowY === 'scroll' ||
style.overflowX === 'auto' ||
style.overflowX === 'scroll') {
return {
// Verify element has scrollable content
return element.scrollHeight > element.clientHeight ||
element.scrollWidth > element.clientWidth;
}
return false;
}
// Helper function to check if element is actually visible
function isElementVisible(element) {
const style = window.getComputedStyle(element);
// Special handling for fixed position elements
if (style.position === 'fixed') {
// Fixed elements are always considered visible if they have dimensions
if (element.offsetWidth > 0 && element.offsetHeight > 0) {
return true;
}
}
// For non-fixed elements, check offsetParent
if (!element.offsetParent && element !== document.body) {
return false;
}
if (style.display === 'none' ||
style.visibility === 'hidden' ||
style.opacity === '0') {
return false;
}
const rect = element.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return false;
}
// For non-fixed elements, check viewport boundaries
if (style.position !== 'fixed') {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
if (rect.right < 0 ||
rect.bottom < 0 ||
rect.left > viewportWidth ||
rect.top > viewportHeight) {
return false;
}
}
return true;
}
// Helper function to get element text
function getElementText(element) {
// Only process direct text nodes
const textNodes = Array.from(element.childNodes)
.filter(node => node.nodeType === Node.TEXT_NODE);
if (textNodes.length === 0) return '';
return textNodes
.map(node => node.textContent.trim())
.filter(text => text.length > 0)
.join(' ');
}
function processElements(element) {
if (!element || !isElementVisible(element)) return [];
const rect = element.getBoundingClientRect();
const styles = window.getComputedStyle(element);
const isFixed = styles.position === 'fixed';
// Get element base info
const elementInfo = {
tag: element.tagName.toLowerCase(),
text,
location: {x: Math.round(rect.left), y: Math.round(rect.top)},
size: {width: Math.round(rect.width), height: Math.round(rect.height)},
interactions,
children
x: Math.round(isFixed ? rect.left : rect.left + window.pageXOffset),
y: Math.round(isFixed ? rect.top : rect.top + window.pageYOffset),
width: Math.round(rect.width),
height: Math.round(rect.height),
interactions: [],
fixed: isFixed
};
// Add ID or suggested click coordinates for any interactive element
if (isClickable(element) || isInputElement(element)) {
const id = element.id;
if (id) {
elementInfo.id = id;
} else {
// Only add suggested click if there's no ID
elementInfo.suggestedClick = {
x: Math.round(elementInfo.x + elementInfo.width / 2),
y: Math.round(elementInfo.y + elementInfo.height / 2)
};
}
}
// Add text only if this isn't going to be an interactive element
const isInteractive = isClickable(element) || isInputElement(element);
if (!isInteractive) {
elementInfo.text = getElementText(element);
}
// Add interactions
if (isClickable(element)) {
elementInfo.interactions.push('click');
elementInfo.buttonText = getElementText(element);
}
if (isInputElement(element)) {
const inputType = element.getAttribute('type') || 'text';
elementInfo.interactions.push(`input:${inputType}`);
const placeholder = element.getAttribute('placeholder');
if (placeholder) {
elementInfo.placeholder = placeholder;
}
}
if (isScrollable(element)) {
elementInfo.interactions.push('scroll');
}
// Special handling for images
if (element.tagName === 'IMG') {
elementInfo.src = element.src;
elementInfo.alt = element.alt;
}
// Process children
elementInfo.children = Array.from(element.children)
.map(child => processElements(child))
.flat()
.filter(child => child !== null);
return elementInfo;
}
function analyzePageContent() {
const body = document.body;
return {
viewport: getViewportInfo(),
content: body ? processElement(body) : null
content: body ? processElements(body) : []
};
}
}

5811
tools/itb/out.txt Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,19 +0,0 @@
<viewport scroll_x="0" scroll_y="0" width="780" height="441" max_scroll_y="1200" max_scroll_x="0">
SocialConnect
<interactive x="16" y="0" width="240" height="40" interactions="text_input" placeholder="Search posts..."/>
Alice Johnson
2 hours ago
Just finished my morning hike! The sunrise was absolutely breathtaking today. 🌄 #MorningHike #Nature #Wellness
<img x="36" y="220" width="648" height="400" alt="Sunrise view from mountain top" src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='648' height='400'><rect width='100%' height='100%' fill='%23ddd'/></svg>"/>
<interactive x="36" y="632" width="216" height="40" interactions="click">
Like
</interactive>
<interactive x="252" y="632" width="216" height="40" interactions="click">
Comment
</interactive>
<interactive x="468" y="632" width="216" height="40" interactions="click">
Share
</interactive>
Bob Smith
Beautiful view! Which trail is this?
</viewport>

View File

@@ -1,32 +0,0 @@
<viewport scroll_y="0" scroll_x="0" height="800" width="1024">
<header class="sticky" y="0" x="0" width="1024" height="50">
<form id="search">
<input type="text" placeholder="Search..." aria-label="Search box" y="10" x="10" width="200" height="30" />
<button type="submit" y="10" x="220" width="30" height="30">🔍</button>
</form>
</header>
<section class="scrollable" y="50" x="0" width="1024" height="200" scroll_y="0" max_scroll="400">
<heading level="2">Scrollable Section</heading>
Lorem ipsum dolor sit amet, consectetur adipiscing elit... [first paragraph]
</section>
<interactive y="260" x="10" width="100" height="30">
<button onclick="showModal()">Show Modal</button>
</interactive>
<interactive y="260" x="120" width="120" height="30">
<link href="#">JavaScript Link</link>
</interactive>
<interactive y="260" x="250" width="100" height="30">
<button role="button" tabindex="0">ARIA Button</button>
</interactive>
<table y="300" x="0" width="1024">
<cell>Cell with mixed formatting</cell>
<cell>Cell with list:
- Item 1
- Item 2</cell>
</table>
</viewport>

View File

@@ -10,8 +10,6 @@ setup(
'bin/itb_scroll',
'bin/itb_click',
'bin/itb_input',
'bin/itb_links',
'bin/itb_forms',
'bin/itb_navigate',
'bin/itb_refresh'
],

View File

@@ -1,90 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<style>
.scrollable-div {
height: 200px;
overflow-y: scroll;
border: 1px solid black;
}
.sticky-header {
position: sticky;
top: 0;
background: white;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 100;
}
</style>
</head>
<body>
<!-- Sticky header with form -->
<header class="sticky-header">
<form id="search">
<input type="text" placeholder="Search..." aria-label="Search box">
<button type="submit">🔍</button>
</form>
</header>
<!-- Nested scrollable content -->
<div class="scrollable-div">
<h2>Scrollable Section</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a bibendum purus, id accumsan lorem. Ut quis diam semper, rutrum justo in, ornare urna. Proin vel consectetur odio. Donec quis odio ut erat lobortis aliquam. Vestibulum vitae tellus interdum, hendrerit odio id, varius urna. Sed nec est tempor, placerat lectus sit amet, aliquam mauris. Duis elementum risus vitae nunc mollis ullamcorper. Donec sodales ac neque at aliquam. Duis eu risus ex. Fusce neque erat, egestas nec placerat in, dapibus quis magna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Ut faucibus nunc tellus, vel consequat dolor suscipit vel. Sed sapien erat, rutrum non dignissim nec, egestas at augue. Nunc interdum lacinia urna a euismod. Integer ullamcorper sit amet sapien non auctor.
</p>
<p>
Nam vitae bibendum nulla, ut tempus enim. Aenean eu iaculis metus, vel rhoncus justo. Sed vel interdum sem. Praesent congue erat sit amet ultrices molestie. Pellentesque pellentesque odio a ullamcorper facilisis. Sed at viverra magna. Aliquam posuere dolor tortor, non aliquam orci bibendum eu. Sed efficitur feugiat congue. Vivamus elementum in metus lacinia gravida.
</p>
<p>
Fusce aliquam, erat ac consequat eleifend, massa neque posuere urna, vel sagittis tortor neque hendrerit augue. Nulla mattis felis non efficitur hendrerit. Pellentesque viverra elementum enim ornare pellentesque. Donec mollis eget leo non congue. Aliquam vel iaculis odio. Mauris eget neque suscipit, tempus mi quis, porta quam. Integer non tempor leo. Pellentesque magna nisl, fermentum sit amet orci ut, posuere dictum erat. Vivamus a lacinia lacus, eleifend ultrices velit. Etiam est arcu, dictum at sapien efficitur, fringilla tincidunt sem. In nec lacus quis diam placerat sollicitudin. Vivamus scelerisque commodo velit, id lacinia orci volutpat quis. Nulla facilisi. Nam nec quam quis lorem vulputate auctor quis a dui.
</p>
<p>
Curabitur a erat nisi. Fusce lacinia, enim quis mollis placerat, eros nulla blandit turpis, id feugiat purus enim ut diam. Etiam et tincidunt turpis, in scelerisque lectus. Nunc vulputate at velit sit amet tincidunt. Cras maximus ultrices enim, sit amet placerat nisi tristique eget. Sed ac neque sed quam laoreet pellentesque nec vel lorem. Nunc sollicitudin arcu ac quam tempor bibendum. Phasellus sollicitudin tortor at porta lobortis. Aenean venenatis ultrices neque ut scelerisque. Nullam aliquam fringilla varius. Etiam eget nunc leo. Proin ornare tempus libero eget molestie. Praesent id ex sit amet ante semper dictum. Mauris ac libero at ipsum dapibus pretium quis at sapien. Praesent ac luctus lacus. Praesent id varius nisl, et luctus ex.
</p>
<iframe src="about:blank" title="Embedded content">
<p>
Sed ullamcorper sapien non nunc venenatis rutrum. Nulla mollis eu augue non mollis. Maecenas molestie congue libero. Nulla non mi sem. Cras sed ipsum et orci porta posuere et in metus. Vivamus gravida leo a risus interdum, et aliquet neque pretium. Duis a mauris nec erat tempus lacinia. Cras a justo vel dui varius mattis sed ac neque. Phasellus blandit massa sed elit consectetur placerat. Sed aliquet et lacus nec posuere. Donec mattis dolor id tortor facilisis consectetur. Phasellus sit amet magna non nisl pretium semper. Sed viverra, mauris vitae ultricies interdum, libero felis aliquet massa, at vehicula elit lacus a diam. Aliquam sed erat et justo tempor mattis eget et neque. Ut fermentum magna ut lorem sodales condimentum. Vestibulum elementum nec urna sed suscipit.
</p>
</iframe>
<div contenteditable="true">
Curabitur a erat nisi. Fusce lacinia, enim quis mollis placerat, eros nulla blandit turpis, id feugiat purus enim ut diam. Etiam et tincidunt turpis, in scelerisque lectus. Nunc vulputate at velit sit amet tincidunt. Cras maximus ultrices enim, sit amet placerat nisi tristique eget. Sed ac neque sed quam laoreet pellentesque nec vel lorem. Nunc sollicitudin arcu ac quam tempor bibendum. Phasellus sollicitudin tortor at porta lobortis. Aenean venenatis ultrices neque ut scelerisque. Nullam aliquam fringilla varius. Etiam eget nunc leo. Proin ornare tempus libero eget molestie. Praesent id ex sit amet ante semper dictum. Mauris ac libero at ipsum dapibus pretium quis at sapien. Praesent ac luctus lacus. Praesent id varius nisl, et luctus ex.
</div>
</div>
<!-- Interactive elements with dynamic content -->
<div>
<button onclick="showModal()">Show Modal</button>
<a href="#" onclick="return false;">JavaScript Link</a>
<div role="button" tabindex="0">ARIA Button</div>
</div>
<!-- Modal with overlay -->
<div class="modal" style="display:none">
<h3>Modal Title</h3>
<form>
<select multiple>
<option>Choice 1</option>
<option>Choice 2</option>
</select>
<input type="range" min="0" max="100">
</form>
</div>
<!-- Complex text layout -->
<table>
<tr>
<td>Cell with <strong>mixed</strong> <em>formatting</em></td>
<td>Cell with list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</td>
</tr>
</table>
</body>
</html>