1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| import asyncio, json, os, subprocess, time, urllib.request, websockets
class CDPCIDeployer: """CDP CI/CD 部署管理器""" def __init__(self, port=9222, headless=True): self.port, self.headless = port, headless self.chrome_proc, self.xvfb_proc = None, None self.ws, self.sid = None, None self._cid = 0 async def _cdp(self, method, params=None, sid=None): self._cid += 1 msg = {"id": self._cid, "method": method, "params": params or {}} if sid or self.sid: msg["sessionId"] = sid or self.sid await self.ws.send(json.dumps(msg)) async for r in self.ws: d = json.loads(r) if d.get("id") == self._cid: return d.get("result", {}) def setup(self): if not self.headless: self.xvfb_proc = subprocess.Popen( ["Xvfb", ":99", "-screen", "0", "1920x1080x24", "-ac"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) os.environ["DISPLAY"] = ":99" time.sleep(1) args = [find_chrome(), f"--remote-debugging-port={self.port}", "--remote-allow-origins=*", "--no-first-run", "--no-default-browser-check", "--disable-dev-shm-usage"] if self.headless: args.append("--headless=new") if os.environ.get("CDP_NO_SANDBOX","true") in ("1","true"): args.append("--no-sandbox") self.chrome_proc = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) start = time.time() while time.time() - start < 30: try: r = urllib.request.urlopen(f"http://127.0.0.1:{self.port}/json/version", timeout=5) if r.status == 200: break except: pass time.sleep(1) async def connect(self): r = urllib.request.urlopen(f"http://127.0.0.1:{self.port}/json/version") url = json.loads(r.read())["webSocketDebuggerUrl"] self.ws = await websockets.connect(url) t = await self._cdp("Target.getTargets") s = await self._cdp("Target.attachToTarget", {"targetId": t["targetInfos"][0]["targetId"], "flatten": True}) self.sid = s["sessionId"] print(f"已连接 CDP: {url}") async def run_test(self, url, test_func): await self._cdp("Page.navigate", {"url": url}) await asyncio.sleep(2) return await test_func(self) async def screenshot(self, path=None): path = path or f"/tmp/screenshots/ss_{int(time.time())}.png" r = await self._cdp("Page.captureScreenshot", {"format": "png"}) import base64 with open(path, "wb") as f: f.write(base64.b64decode(r["data"])) return path def cleanup(self): if self.ws: asyncio.run_coroutine_threadsafe(self.ws.close(), asyncio.get_event_loop()) for p in [self.chrome_proc, self.xvfb_proc]: if p: p.terminate() try: p.wait(timeout=5) except: p.kill() print("资源已清理") async def __aenter__(self): self.setup(); await self.connect(); return self async def __aexit__(self, *a): self.cleanup()
|