Spaces:
Sleeping
Sleeping
File size: 7,695 Bytes
6fa6086 25e8e6c 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 25e8e6c 955de62 6fa6086 374ae42 955de62 6fa6086 955de62 6fa6086 955de62 6fa6086 0d748b1 6fa6086 955de62 6fa6086 0d748b1 6fa6086 955de62 6fa6086 0d748b1 955de62 6fa6086 0d748b1 6fa6086 955de62 0d748b1 6fa6086 0d748b1 | 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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | import gradio as gr
import spaces
import json
import os
import subprocess
import asyncio
import threading
import time
# βββ Install browser deps + Xvfb on startup βββββββββββββββββββ
def setup():
"""Install chromium deps and start Xvfb"""
# Install system deps for chromium
subprocess.run(
"apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 "
"libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 "
"libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 "
"libasound2 libx11-xcb1 libxcb1 libxext6 libx11-6 libxshmfence1 "
"xvfb fonts-liberation 2>/dev/null",
shell=True, capture_output=True
)
# Install playwright browsers
subprocess.run(["python", "-m", "playwright", "install", "chromium"], capture_output=True)
# Start Xvfb
subprocess.Popen(
["Xvfb", ":99", "-screen", "0", "1280x720x24", "-nolisten", "tcp"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
os.environ["DISPLAY"] = ":99"
time.sleep(1)
print("[β] Setup done: deps + Xvfb")
setup()
# βββ Playwright Async Browser βββββββββββββββββββββββββββββββββ
from playwright.async_api import async_playwright
pw = None
browser = None
sessions = {} # session_id -> page
ready = False
lock = asyncio.Lock()
async def init_browser():
global pw, browser, ready
try:
pw = await async_playwright().start()
browser = await pw.chromium.launch(
headless=True,
args=[
"--no-sandbox",
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-first-run",
"--single-process",
]
)
ready = True
print("[β] Browser ready (headless)")
except Exception as e:
print(f"[β] Browser init failed: {e}")
async def get_page(session_id="default"):
if session_id not in sessions:
ctx = await browser.new_context(
viewport={"width": 1280, "height": 720},
user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
page = await ctx.new_page()
await page.set_extra_http_headers({"Accept-Language": "en-US,en;q=0.9"})
sessions[session_id] = page
return sessions[session_id]
# βββ API Functions (async) βββββββββββββββββββββββββββββββββββββ
async def run_code(url: str, script: str, wait: float, session: str) -> str:
"""Navigate to URL and execute JavaScript"""
global ready
if not ready:
await init_browser()
if not ready:
return json.dumps({"success": False, "error": "Browser failed to start"})
async with lock:
try:
page = await get_page(session or "default")
if url and url.strip():
await page.goto(url.strip(), wait_until="domcontentloaded", timeout=60000)
if wait and wait > 0:
await page.wait_for_timeout(int(wait * 1000))
if not script or not script.strip():
return json.dumps({"success": True, "result": None, "url": page.url})
result = await page.evaluate(script.strip())
return json.dumps({"success": True, "result": result}, ensure_ascii=False, default=str)
except Exception as e:
return json.dumps({"success": False, "error": str(e)})
async def eval_code(script: str, session: str) -> str:
"""Execute JS on existing page"""
if not ready:
return json.dumps({"success": False, "error": "Browser not ready"})
async with lock:
try:
sid = session or "default"
if sid not in sessions:
return json.dumps({"success": False, "error": f"Session '{sid}' not found. Use Run tab first."})
page = sessions[sid]
result = await page.evaluate(script.strip())
return json.dumps({"success": True, "result": result}, ensure_ascii=False, default=str)
except Exception as e:
return json.dumps({"success": False, "error": str(e)})
async def list_sessions() -> str:
info = {}
for sid, page in sessions.items():
try:
info[sid] = page.url
except:
info[sid] = "?"
return json.dumps({"ready": ready, "sessions": info})
async def close_session(session: str) -> str:
async with lock:
if session in sessions:
try:
await sessions[session].context.close()
except:
pass
del sessions[session]
return json.dumps({"success": True, "closed": session})
return json.dumps({"success": False, "error": "not found"})
async def health_check() -> str:
return json.dumps({"ready": ready, "sessions": len(sessions)})
@spaces.GPU(duration=0)
def _gpu_dummy():
"""Dummy function to satisfy ZeroGPU detection"""
pass
# βββ Init browser at startup ββββββββββββββββββββββββββββββββββ
def startup_sync():
"""Run async init in a new event loop (avoids deprecation)"""
loop = asyncio.new_event_loop()
loop.run_until_complete(init_browser())
loop.close()
threading.Thread(target=startup_sync, daemon=True).start()
time.sleep(5)
# βββ Gradio UI ββββββββββββββββββββββββββββββββββββββββββββββββ
with gr.Blocks(title="Playwright Runner") as demo:
gr.Markdown("# π Playwright Remote Runner\nSend JS to execute in Chromium. Use API for programmatic access.")
with gr.Tab("Run"):
with gr.Row():
url_input = gr.Textbox(label="URL", placeholder="https://example.com")
session_input = gr.Textbox(label="Session", value="default")
wait_input = gr.Number(label="Wait (sec)", value=3)
script_input = gr.Code(label="JavaScript", language="javascript", value='() => document.title')
run_btn = gr.Button("Execute", variant="primary")
run_output = gr.Code(label="Result", language="json")
run_btn.click(run_code, [url_input, script_input, wait_input, session_input], run_output, queue=False)
with gr.Tab("Eval"):
eval_session = gr.Textbox(label="Session", value="default")
eval_script = gr.Code(label="JavaScript", language="javascript",
value='() => ({ title: document.title, url: location.href })')
eval_btn = gr.Button("Eval", variant="primary")
eval_output = gr.Code(label="Result", language="json")
eval_btn.click(eval_code, [eval_script, eval_session], eval_output, queue=False)
with gr.Tab("Sessions"):
list_btn = gr.Button("List")
list_output = gr.Code(language="json")
list_btn.click(list_sessions, [], list_output, queue=False)
close_input = gr.Textbox(label="Close session")
close_btn = gr.Button("Close", variant="stop")
close_output = gr.Code(language="json")
close_btn.click(close_session, [close_input], close_output, queue=False)
with gr.Tab("Health"):
h_btn = gr.Button("Check")
h_out = gr.Code(language="json")
h_btn.click(health_check, [], h_out, queue=False)
demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False, show_api=True)
|