Skip to content

Commit

Permalink
Changes to the selector stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
ankur22 committed Dec 9, 2024
1 parent 123121f commit b60e79a
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 79 deletions.
20 changes: 18 additions & 2 deletions common/js/injected_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,23 @@ class CSSQueryEngine {

class TextQueryEngine {
queryAll(root, selector) {
return root.queryAll(selector);
// Remove the `text=` prefix if present
const text = selector.startsWith('text=') ? selector.slice(5) : selector;

// Query all elements within the root
const elements = Array.from(root.querySelectorAll('*'));

// Filter elements based on their text content
const e = elements.filter((element) => {
const elementText = element.textContent.trim();
return elementText.includes(text);
});

if (e.length > 0) {
return [e[0]]
}

return e
}
}

Expand Down Expand Up @@ -215,7 +231,7 @@ class RoleQueryEngine {
dialog: ["dialog"],
img: ["img[alt]"],
form: ["form"],
textbox: ["input[type='text']", "textarea"],
textbox: ["input[type='text']", "input[type='email']", "input[type='password']", "textarea"],
radio: ["input[type='radio']"],
// Add more implicit roles as needed
};
Expand Down
157 changes: 80 additions & 77 deletions common/js/selector_engine.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
(() => {
// Selector Finder Function
function findBestSelector(element) {
// 1. Check for `data-testid`
if (element.hasAttribute('data-testid')) {
// Prefer aria-label or aria-labelledby
if (!element.hasAttribute('aria-label')) {
// 1. Check for `data-testid`
if (element.hasAttribute('data-testid')) {
return `'[data-testid="${element.getAttribute('data-testid')}"]'`;
}
}

// 2. Check for `id`
if (element.id) {
// 2. Check for `id`
if (element.id) {
return `'#${element.id}'`;
}
}

// 3. Check for role and accessible name (explicit or implicit roles)
const role = getRole(element);
if (role) {
const name = getAccessibleName(element);
if (name) {
return `'role=${role}[name="${name}"]'`;
}
return `'role=${role}'`;
const name = getAccessibleName(element);
if (name) {
return `'role=${role}[name="${name}"]'`;
}
return `'role=${role}'`;
}

// 4. Check for visible text
const text = element.textContent.trim();
if (text) {
return `'text="${text}"'`;
return `'text="${text}"'`;
}

// 5. Fallback to XPath
Expand All @@ -33,64 +36,64 @@

// Helper function to compute the role (explicit or implicit)
function getRole(element) {
// Check for explicit role
if (element.hasAttribute('role')) {
return element.getAttribute('role');
}
// Check for explicit role
if (element.hasAttribute('role')) {
return element.getAttribute('role');
}

// Implicit role mapping
const implicitRoles = {
button: ['button', "input[type='button']", "input[type='submit']", "input[type='reset']"],
link: ['a[href]'],
checkbox: ["input[type='checkbox']"],
heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
dialog: ['dialog'],
img: ['img[alt]'],
textbox: ["input[type='text']", "input[type='email']", "input[type='password']", 'textarea'],
radio: ["input[type='radio']"],
// Add more implicit roles if needed
};

// Implicit role mapping
const implicitRoles = {
button: ['button', "input[type='button']", "input[type='submit']", "input[type='reset']"],
link: ['a[href]'],
checkbox: ["input[type='checkbox']"],
heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
dialog: ['dialog'],
img: ['img[alt]'],
textbox: ["input[type='text']", 'textarea'],
radio: ["input[type='radio']"],
// Add more implicit roles if needed
};

for (const [role, selectors] of Object.entries(implicitRoles)) {
for (const selector of selectors) {
if (element.matches(selector)) {
return role;
}
}
for (const [role, selectors] of Object.entries(implicitRoles)) {
for (const selector of selectors) {
if (element.matches(selector)) {
return role;
}
}
}

return null;
return null;
}

// Helper function to compute the accessible name of an element
function getAccessibleName(element) {
// Prefer aria-label or aria-labelledby
if (element.hasAttribute('aria-label')) {
return element.getAttribute('aria-label');
}
if (element.hasAttribute('aria-labelledby')) {
const labelId = element.getAttribute('aria-labelledby');
const labelElement = element.ownerDocument.getElementById(labelId);
return labelElement ? labelElement.textContent.trim() : '';
}
// Use text content as a fallback
return element.textContent.trim();
// Prefer aria-label or aria-labelledby
if (element.hasAttribute('aria-label')) {
return element.getAttribute('aria-label');
}
if (element.hasAttribute('aria-labelledby')) {
const labelId = element.getAttribute('aria-labelledby');
const labelElement = element.ownerDocument.getElementById(labelId);
return labelElement ? labelElement.textContent.trim() : '';
}
// Use text content as a fallback
return element.textContent.trim();
}

// Helper function to generate XPath as a fallback
function generateXPath(element) {
if (element.id) {
return `'//*[@id="${element.id}"]'`;
}
const siblings = Array.from(element.parentNode.children).filter(
(el) => el.nodeName === element.nodeName
);
const index = siblings.indexOf(element) + 1;
const tagName = element.nodeName.toLowerCase();
if (element.parentNode === document) {
return `'/${tagName}[${index}]'`;
}
return `'${generateXPath(element.parentNode)}/${tagName}[${index}]'`;
if (element.id) {
return `'//*[@id="${element.id}"]'`;
}
const siblings = Array.from(element.parentNode.children).filter(
(el) => el.nodeName === element.nodeName
);
const index = siblings.indexOf(element) + 1;
const tagName = element.nodeName.toLowerCase();
if (element.parentNode === document) {
return `'/${tagName}[${index}]'`;
}
return `'${generateXPath(element.parentNode)}/${tagName}[${index}]'`;
}

// Highlight and Selector Display
Expand All @@ -108,28 +111,28 @@

// Helper to copy text to clipboard
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
console.log(`Copied to clipboard: ${text}`);
showTemporaryMessage('Copied!', selectorOverlay);
}).catch((err) => {
console.error('Failed to copy text: ', err);
showTemporaryMessage('Failed to copy', selectorOverlay);
});
navigator.clipboard.writeText(text).then(() => {
console.log(`Copied to clipboard: ${text}`);
showTemporaryMessage('Copied!', selectorOverlay);
}).catch((err) => {
console.error('Failed to copy text: ', err);
showTemporaryMessage('Failed to copy', selectorOverlay);
});
}

// Show a temporary message in the overlay
function showTemporaryMessage(message, overlay) {
const originalText = overlay.textContent;
overlay.textContent = message;
setTimeout(() => {
overlay.textContent = originalText;
}, 1000); // Reset after 1 second
const originalText = overlay.textContent;
overlay.textContent = message;
setTimeout(() => {
overlay.textContent = originalText;
}, 1000); // Reset after 1 second
}

// Highlight the element and show selector
function highlightElement(event) {
if (lastHighlightedElement) {
lastHighlightedElement.style.outline = '';
lastHighlightedElement.style.outline = '';
}
const element = event.target;
element.style.outline = '2px solid #FF671D';
Expand All @@ -143,17 +146,17 @@

// Copy to clipboard on Command + C
document.onkeydown = (e) => {
if (e.metaKey && e.key === 'c') { // Press Command + C
e.preventDefault();
copyToClipboard(selector);
}
if (e.metaKey && e.key === 'c') { // Press Command + C
e.preventDefault();
copyToClipboard(selector);
}
};
}

function removeHighlight() {
if (lastHighlightedElement) {
lastHighlightedElement.style.outline = '';
lastHighlightedElement = null;
lastHighlightedElement.style.outline = '';
lastHighlightedElement = null;
}
selectorOverlay.textContent = '';
document.onkeydown = null; // Remove keydown listener
Expand Down

0 comments on commit b60e79a

Please sign in to comment.