"""Carrega a extensão num Firefox headless via Marionette, confirma que o background script inicializou e que os itens de menu foram criados, e exercita insert.js contra um input real, um textarea e um contenteditable.""" import json, os, socket, subprocess, tempfile, time, shutil, sys EXT = os.path.dirname(os.path.abspath(__file__)) PORT = 2929 class M: def __init__(self, port): self.s = socket.create_connection(("127.0.0.1", port), timeout=60) self.s.settimeout(60) self.buf = b"" self._read() # handshake self.msgid = 0 def _read(self): while b":" not in self.buf: self.buf += self.s.recv(65536) n, _, rest = self.buf.partition(b":") n = int(n) while len(rest) < n: rest += self.s.recv(65536) self.buf = rest[n:] return json.loads(rest[:n]) def cmd(self, name, params=None): self.msgid += 1 payload = json.dumps([0, self.msgid, name, params or {}]).encode() self.s.sendall(str(len(payload)).encode() + b":" + payload) while True: msg = self._read() if isinstance(msg, list) and msg[0] == 1 and msg[1] == self.msgid: if msg[2]: raise RuntimeError(f"{name}: {msg[2]}") return msg[3] if not shutil.which("firefox"): print("firefox não encontrado, pulando") raise SystemExit(0) profile = tempfile.mkdtemp(prefix="ffprof-") with open(os.path.join(profile, "user.js"), "w") as f: f.write('user_pref("xpinstall.signatures.required", false);\n') f.write('user_pref("extensions.autoDisableScopes", 0);\n') f.write('user_pref("marionette.port", %d);\n' % PORT) f.write('user_pref("browser.shell.checkDefaultBrowser", false);\n') proc = subprocess.Popen( ["firefox", "--marionette", "-remote-allow-system-access", "--headless", "--no-remote", "--profile", profile], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) def cleanup(): proc.terminate() try: proc.wait(timeout=10) except Exception: proc.kill() shutil.rmtree(profile, ignore_errors=True) try: for _ in range(60): try: m = M(PORT); break except OSError: time.sleep(1) else: raise SystemExit("marionette não subiu") m.cmd("WebDriver:NewSession", {"capabilities": {}}) res = m.cmd("Addon:Install", {"path": EXT, "temporary": True}) addon_id = res.get("value", res) print("extensão instalada:", addon_id) time.sleep(3) # Página de teste com os três tipos de campo editável. html = "file://" + os.path.join(EXT, "testdata.html") m.cmd("WebDriver:Navigate", {"url": html}) # Injeta insert.js no conteúdo e valida cada alvo. src = open(os.path.join(EXT, "insert.js")).read() script = src + """ const out = []; for (const id of ["a","b","c"]) { const el = document.getElementById(id); el.focus(); if (el.isContentEditable) { const r = document.createRange(); r.selectNodeContents(el); r.collapse(false); const s = getSelection(); s.removeAllRanges(); s.addRange(r); } const ok = insertIntoFocused("123.456.789-09"); const val = el.isContentEditable ? el.textContent : el.value; out.push(id + ":" + ok + ":" + val); } return out.join("|"); """ r = m.cmd("WebDriver:ExecuteScript", {"script": script, "args": [], "newSandbox": True}) inserted = r.get("value") print("inserção:", inserted) for part in inserted.split("|"): field, ok, value = part.split(":", 2) assert ok == "true", f"insert falhou em #{field}" assert "123.456.789-09" in value, f"valor ausente em #{field}: {value}" # Confere, no processo da extensão, que os menus foram registrados. m.cmd("Marionette:SetContext", {"value": "chrome"}) probe = """ const { ExtensionParent } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionParent.sys.mjs"); const pol = WebExtensionPolicy.getByID("cpfgen@lucasf.xyz"); if (!pol) return "SEM POLICY"; const ctx = ExtensionParent.GlobalManager.getExtension("cpfgen@lucasf.xyz"); const mm = ctx && ctx.apiManager; const menus = ChromeUtils.importESModule( "resource://gre/modules/ExtensionParent.sys.mjs"); let ids = []; try { const m = ctx.apiManager.global.gMenuMap; const ext = [...m.keys()].find(e => e.id === "cpfgen@lucasf.xyz"); ids = ext ? [...m.get(ext).keys()] : []; } catch (e) { ids = ["ERRO:" + e.message]; } return JSON.stringify({active: pol.active, bg: !!ctx, menus: ids}); """ r2 = m.cmd("WebDriver:ExecuteScript", {"script": probe, "args": []}) print("estado:", r2.get("value")) state = json.loads(r2.get("value")) assert state["active"] and state["bg"], "extensão não ficou ativa" esperados = { "cpf", "cnpj", "cpf-formatted", "cpf-raw", "cnpj-formatted", "cnpj-raw", "cnpj-alpha-formatted", "cnpj-alpha-raw", } faltando = esperados - set(state["menus"]) assert not faltando, f"itens de menu ausentes: {faltando}" print("\nOK: extensão ativa, 8 itens de menu, inserção nos 3 tipos de campo") finally: cleanup()