Some checks failed
Beta Release / beta (push) Failing after 48s
Adds a WXT-based browser extension that replaces manual JS snippet injection for AI-driven browser testing. The extension auto-connects to the Muyue server via WebSocket on every page, using the exact same protocol as the existing snippet — zero backend changes needed. - Chrome/Edge (MV3) + Firefox (MV2) from single codebase via WXT - Content script: auto-connect WS, console capture, URL tracking, RPC - Background service worker: token management, screenshots, badge - Popup + side panel with server status, sessions, URL config - CI workflows: build extension, attach .zip to releases - Makefile targets: ext, ext-chrome, ext-firefox, ext-zip - Version bumped to 0.8.0 Assisted-by: GLM-5.1 via Crush <crush@charm.land>
114 lines
3.0 KiB
JavaScript
114 lines
3.0 KiB
JavaScript
let lastList = [];
|
|
|
|
function safeText(el) {
|
|
let t = (el.innerText || el.textContent || '').trim();
|
|
if (t.length > 80) t = t.slice(0, 80) + '…';
|
|
return t;
|
|
}
|
|
|
|
function describe(el) {
|
|
let sel = el.id ? '#' + el.id : el.tagName.toLowerCase();
|
|
if (!el.id && el.className && typeof el.className === 'string') {
|
|
sel += '.' + el.className.trim().split(/\s+/).slice(0, 2).join('.');
|
|
}
|
|
const label = el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('name') || '';
|
|
return {
|
|
tag: el.tagName.toLowerCase(),
|
|
selector: sel,
|
|
text: safeText(el),
|
|
label,
|
|
type: el.getAttribute('type') || '',
|
|
disabled: !!el.disabled,
|
|
};
|
|
}
|
|
|
|
export function listClickables() {
|
|
const els = Array.from(
|
|
document.querySelectorAll(
|
|
'button, a[href], input[type=submit], input[type=button], [role=button], [onclick]'
|
|
)
|
|
);
|
|
lastList = els.filter((e) => {
|
|
const r = e.getBoundingClientRect();
|
|
return r.width > 0 && r.height > 0;
|
|
});
|
|
return lastList.map((el, i) => {
|
|
const d = describe(el);
|
|
d.index = i;
|
|
return d;
|
|
});
|
|
}
|
|
|
|
export function clickElement(params) {
|
|
let el;
|
|
if (params.selector) el = document.querySelector(params.selector);
|
|
else if (typeof params.index === 'number') el = lastList[params.index];
|
|
if (!el) return { ok: false, error: 'element not found' };
|
|
if (el.disabled) return { ok: false, error: 'element is disabled' };
|
|
try {
|
|
el.scrollIntoView({ block: 'center' });
|
|
el.click();
|
|
return { ok: true };
|
|
} catch (e) {
|
|
return { ok: false, error: String(e) };
|
|
}
|
|
}
|
|
|
|
export function typeText(params) {
|
|
let el;
|
|
if (params.selector) el = document.querySelector(params.selector);
|
|
else if (typeof params.index === 'number') el = lastList[params.index];
|
|
if (!el) return { ok: false, error: 'element not found' };
|
|
const proto = Object.getPrototypeOf(el);
|
|
const setter = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
try {
|
|
if (setter && setter.set) setter.set.call(el, params.text || '');
|
|
else el.value = params.text || '';
|
|
} catch {
|
|
el.value = params.text || '';
|
|
}
|
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
return { ok: true };
|
|
}
|
|
|
|
export function evalExpr(params) {
|
|
try {
|
|
const r = (0, eval)(params.expr);
|
|
return { ok: true, value: serialize(r) };
|
|
} catch (e) {
|
|
return { ok: false, error: String(e) };
|
|
}
|
|
}
|
|
|
|
export function currentUrl() {
|
|
return { url: location.href, title: document.title };
|
|
}
|
|
|
|
function serialize(v) {
|
|
if (v === undefined) return 'undefined';
|
|
try {
|
|
return JSON.parse(JSON.stringify(v));
|
|
} catch {
|
|
return String(v);
|
|
}
|
|
}
|
|
|
|
export function dispatch(msg) {
|
|
const p = msg.params || {};
|
|
switch (msg.action) {
|
|
case 'list_clickables':
|
|
return listClickables();
|
|
case 'click':
|
|
return clickElement(p);
|
|
case 'eval':
|
|
return evalExpr(p);
|
|
case 'current_url':
|
|
return currentUrl();
|
|
case 'type':
|
|
return typeText(p);
|
|
default:
|
|
return { ok: false, error: 'unknown action: ' + msg.action };
|
|
}
|
|
}
|