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

@@ -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) : []
};
}
}