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
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "xpath-chatbot",
"name": "test-pilot",
"version": "1.1.0",
"description": "Premium AI Chatbot for robust XPath and Locator generation.",
"description": "Test Pilot is a browser extension that helps you to generate XPath and Locator, perform Load test, Broken link/image test, and more for your web application.",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -27,4 +27,4 @@
"jspdf": "^4.0.0",
"jspdf-autotable": "^5.0.7"
}
}
}
4 changes: 2 additions & 2 deletions src/public/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<div class="container">
<header>
<h1>TestPilot</h1>
<p class="subtitle">Capture DOM Context for Q&A</p>
<p class="subtitle">Capture DOM Context for QA</p>
</header>

<main>
Expand Down Expand Up @@ -43,7 +43,7 @@ <h1>TestPilot</h1>
</main>

<footer>
<p>Optimized for token limits & performance</p>
<p>Optimized for limit & performance</p>
</footer>
</div>
<script type="module" src="../scripts/popup.js"></script>
Expand Down
24 changes: 7 additions & 17 deletions src/public/sidepanel.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</svg>
</span>
<div class="title-group">
<h2>TestPilot AI Chat Panel</h2>
<h2>TestPilot Chat Panel</h2>
<div class="framework-switcher" id="framework-switcher">
<button class="switcher-trigger" id="switcher-trigger">
<span class="current-icon" id="current-framework-icon">
Expand Down Expand Up @@ -265,23 +265,13 @@ <h3>
</main>

<footer class="input-area">
<div id="page-status" style="font-size: 0.7rem; color: #94a3b8; margin-bottom: 8px; text-align: right;">
Syncing...</div>
<div id="typing-indicator" class="typing-indicator hidden">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
AI is thinking...
<div id="page-status" class="page-status">
<span class="status-dot"></span>
<span class="status-text">Syncing...</span>
</div>
<div class="input-wrapper">
<textarea id="user-input" placeholder="Ask about the page..." rows="1"></textarea>
<button id="send-btn" disabled>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</button>
<div class="footer-links">
<a href="https://charancherry007.github.io/TestPilot/" target="_blank" class="privacy-link">Privacy
Policy</a>
</div>
</footer>
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
return true;
}
});

// Auto-Sync on Refresh: Notify sidepanel when a tab finishes loading
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' && tab.url && !tab.url.startsWith('chrome://')) {
chrome.runtime.sendMessage({
action: "tab_refreshed",
tabId: tabId
}).catch(() => {
// Ignore errors when sidepanel/popup is not open
console.error("Failed to send message to TestPilot");
});
}
});
3 changes: 3 additions & 0 deletions src/scripts/modules/xpath-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ export function formatLocator(xpath, el, attrType, value, framework) {
} else if (framework === 'selenium') {
if (attrType === 'id') return `By.id("${value}")`;
if (attrType === 'class') return `By.className("${value}")`;
if (attrType === 'data-testid') return `By.xpath("//*[@data-testid='" + ${value} + "']")`;
if (attrType === 'role') return `By.xpath("//*[@role='" + ${value} + "']")`;
if (attrType === 'text') return `By.xpath("//*[contains(text(), '" + ${value} + "')]")`;
return `By.xpath("${xpath}")`;
}
return xpath;
Expand Down
53 changes: 16 additions & 37 deletions src/scripts/sidepanel/sidepanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ Chart.register(...registerables);

document.addEventListener('DOMContentLoaded', () => {
const chatWindow = document.getElementById('chat-window');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const clearBtn = document.getElementById('clear-chats');
const syncBtn = document.getElementById('sync-dom');
const inspectBtn = document.getElementById('inspect-btn');
const pageStatus = document.getElementById('page-status');
const typingIndicator = document.getElementById('typing-indicator');
const statusText = pageStatus.querySelector('.status-text');
const lockedBar = document.getElementById('locked-context');
const lockedName = document.getElementById('locked-element-name');
const clearLockedBtn = document.getElementById('clear-locked');
Expand Down Expand Up @@ -60,21 +58,6 @@ document.addEventListener('DOMContentLoaded', () => {

// --- Interactive Listeners ---

userInput.addEventListener('input', () => {
userInput.style.height = 'auto';
userInput.style.height = userInput.scrollHeight + 'px';
sendBtn.disabled = userInput.value.trim() === '';
});

userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleUserInput();
}
});

sendBtn.addEventListener('click', () => handleUserInput());

clearBtn.addEventListener('click', () => {
chatWindow.innerHTML = '';
messageHistory = [];
Expand Down Expand Up @@ -188,7 +171,7 @@ document.addEventListener('DOMContentLoaded', () => {

chrome.tabs.sendMessage(tab.id, { action: "extract_images" }, async (response) => {
if (chrome.runtime.lastError) {
addBotMessage("❌ Connection Error: I can't reach the page content. Please refresh the tab and try again.");
addBotMessage("❌ Error: I can't reach the page content. Please refresh the tab and Sync DOM.");
checkImagesBtn.classList.remove('loading');
return;
}
Expand Down Expand Up @@ -586,6 +569,9 @@ document.addEventListener('DOMContentLoaded', () => {
lockedName.textContent = `${lockedElement.tag}${lockedElement.attributes.id ? '#' + lockedElement.attributes.id : ''}`;
lockedBar.classList.remove('hidden');
processAIResponse("generate stable xpath");
} else if (request.action === "tab_refreshed") {
// Auto-sync DOM when the associated tab is refreshed
syncContext();
}
});

Expand Down Expand Up @@ -624,44 +610,40 @@ document.addEventListener('DOMContentLoaded', () => {
}

async function syncContext() {
pageStatus.textContent = "Syncing...";
statusText.textContent = "Syncing...";
pageStatus.className = 'page-status';
const tab = await getActiveTab();
if (!tab) {
pageStatus.textContent = "Offline";
statusText.textContent = "Offline";
pageStatus.classList.add('offline');
return;
}

chrome.tabs.sendMessage(tab.id, { action: "capture_dom" }, (response) => {
if (chrome.runtime.lastError) {
pageStatus.textContent = "Error";
statusText.textContent = "Error";
pageStatus.classList.add('error');
addBotMessage("❌ Connection Failed: I can't reach this page. Please refresh the browser tab to reconnect.");
return;
}

if (response?.data) {
currentContext = response.data;
pageStatus.textContent = "Synced";
statusText.textContent = "Synced";
pageStatus.classList.add('synced');
if (currentContext.nodeLimitReached) {
addBotMessage("⚠️ Optimized large page capture active.");
}
} else {
pageStatus.textContent = "Error";
statusText.textContent = "Error";
pageStatus.classList.add('error');
}
});
}

// Initialize
syncContext();

function handleUserInput() {
const text = userInput.value.trim();
if (!text) return;
addUserMessage(text);
userInput.value = '';
userInput.style.height = 'auto';
processAIResponse(text);
}

function addUserMessage(text) {
const msgDiv = document.createElement('div');
msgDiv.className = 'message user-msg';
Expand All @@ -680,11 +662,10 @@ document.addEventListener('DOMContentLoaded', () => {

async function processAIResponse(query) {
if (!hasConsent) {
addBotMessage("Please enable AI Analysis to proceed.");
addBotMessage("Please enable TestPilot Analysis to proceed.");
return;
}

typingIndicator.classList.remove('hidden');
await new Promise(r => setTimeout(r, 600));

let bestMatch = lockedElement;
Expand All @@ -694,7 +675,6 @@ document.addEventListener('DOMContentLoaded', () => {
}

if (!bestMatch) {
typingIndicator.classList.add('hidden');
addBotMessage("Couldn't find target. Try selecting it first.");
return;
}
Expand Down Expand Up @@ -731,7 +711,6 @@ document.addEventListener('DOMContentLoaded', () => {
responseText += `Strength: ${sanitizedStrength}\n\n`;
});

typingIndicator.classList.add('hidden');
await streamBotMessage(responseText);
}

Expand Down
62 changes: 61 additions & 1 deletion src/styles/sidepanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,12 @@ input:checked+.slider:before {
}

.input-area {
padding: 16px;
padding: 12px 16px;
background: var(--panel-bg);
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: center;
}

.typing-indicator {
Expand All @@ -606,6 +609,53 @@ input:checked+.slider:before {
gap: 4px;
}

.page-status {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
font-size: 0.7rem;
color: var(--text-muted);
margin-bottom: 8px;
}

.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #94a3b8;
/* Default muted */
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}

.page-status.synced .status-dot {
background-color: #22c55e;
box-shadow: 0 0 6px rgba(34, 197, 94, 0.4);
}

.page-status.error .status-dot,
.page-status.offline .status-dot {
background-color: #ef4444;
}

.footer-links {
margin-top: 4px;
}

.privacy-link {
font-size: 0.65rem;
color: var(--text-muted);
text-decoration: none;
transition: color 0.2s;
opacity: 0.8;
}

.privacy-link:hover {
color: var(--primary);
text-decoration: underline;
opacity: 1;
}

.dot {
width: 4px;
height: 4px;
Expand Down Expand Up @@ -645,6 +695,16 @@ input:checked+.slider:before {
gap: 12px;
}

.input-wrapper.disabled {
opacity: 0.6;
background: rgba(255, 255, 255, 0.02);
cursor: not-allowed;
}

.input-wrapper.disabled textarea {
cursor: not-allowed;
}

#user-input {
flex: 1;
background: transparent;
Expand Down
13 changes: 13 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ export default defineConfig({
sidepanel: 'src/public/sidepanel.html',
popup: 'src/public/popup.html'
},
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('chart.js')) {
return 'vendor-charts';
}
if (id.includes('jspdf')) {
return 'vendor-pdf';
}
return 'vendor';
}
}
}
},
},
});