§
    %ã¿jÀ  ã                  ó  — d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
 ddlmZmZmZmZ  e
e¦  «                             ¦   «         j        j        j        Zedz  dz  dz  d	z  Zd
Zdd d„Zd!d"d„Zd#d$d„Zd%d„Zd&d„ZdS )'z©
FreeExile Real-Browser QA Automation Harness Utilities.
Provides server liveness management, Playwright browser bootstrap,
and in-page telemetry probe instrumentation.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚOptionalÚTupleÚdocsÚqaÚreportsÚ	telemetrya–	  
(() => {
  if (window.__qaProbeReady) return;
  window.__qaProbeReady = true;

  window.__qaTelemetry = {
    frameTimes: [],
    jankCount: 0,
    lastFrameTime: performance.now(),
    navCount: 1,
    active: true,
    inputDrops: 0,
    init() {
      window.addEventListener('beforeunload', () => { this.navCount++; });
      const record = (now) => {
        if (!this.active) return;
        const dt = now - this.lastFrameTime;
        this.lastFrameTime = now;
        if (dt > 0 && dt < 500) {
          this.frameTimes.push(Number(dt.toFixed(2)));
          if (dt > 33.33) this.jankCount++;
        }
        requestAnimationFrame(record);
      };
      requestAnimationFrame(record);
    },
    getFpsStats() {
      const times = this.frameTimes.slice();
      if (times.length === 0) return { avgFps: 60, minFps: 60, maxFps: 60, p95: 16.6, p99: 16.6, count: 0 };
      times.sort((a, b) => a - b);
      const avg = times.reduce((s, v) => s + v, 0) / times.length;
      const minDt = times[0];
      const maxDt = times[times.length - 1];
      const p95 = times[Math.floor(times.length * 0.95)] || maxDt;
      const p99 = times[Math.floor(times.length * 0.99)] || maxDt;
      return {
        sampleCount: times.length,
        avgFps: Number((1000 / avg).toFixed(1)),
        minFps: Number((1000 / maxDt).toFixed(1)),
        maxFps: Number((1000 / minDt).toFixed(1)),
        avgFrameTimeMs: Number(avg.toFixed(2)),
        p95FrameTimeMs: Number(p95.toFixed(2)),
        p99FrameTimeMs: Number(p99.toFixed(2)),
        jankCount: this.jankCount,
        jankRatio: Number(((this.jankCount / times.length) * 100).toFixed(2))
      };
    },
    getMemorySnapshot() {
      const mem = window.performance?.memory || {};
      const tmrMem = window.TileMapRenderer?.getMemoryUsage?.() || 0;
      return {
        usedHeapMB: Number(((mem.usedJSHeapSize || 0) / (1024 * 1024)).toFixed(2)),
        totalHeapMB: Number(((mem.totalJSHeapSize || 0) / (1024 * 1024)).toFixed(2)),
        tmrBytes: tmrMem,
        tmrMB: Number((tmrMem / (1024 * 1024)).toFixed(2)),
        domNodes: document.getElementsByTagName('*').length,
        canvasCount: document.getElementsByTagName('canvas').length,
        activeParticles: Array.isArray(window.particles) ? window.particles.length : 0,
        activeMonsters: Array.isArray(window.activeMonsters) ? window.activeMonsters.length : 0
      };
    }
  };
  window.__qaTelemetry.init();
})();
ú	127.0.0.1é˜  ÚhostÚstrÚportÚintÚreturnÚboolc                óè   — t          j         t           j        t           j        ¦  «        5 }|                     d¦  «         |                     | |f¦  «        dk    cddd¦  «         S # 1 swxY w Y   dS )z3Checks whether the specified TCP port is listening.gš™™™™™é?r   N)ÚsocketÚAF_INETÚSOCK_STREAMÚ
settimeoutÚ
connect_ex)r   r   Úss      ú2C:\Projects\FreeExile\tools\qa\qa_harness_utils.pyÚis_port_openr   W   s¤   € å	Œ•v”~¥vÔ'9Ñ	:Ô	:ð /¸aØ	�Š�SÑÔÐØ�|Š|˜T 4˜LÑ)Ô)¨QÒ.ð/ð /ð /ð /ñ /ô /ð /ð /ð /ð /ð /ð /øøøð /ð /ð /ð /ð /ð /s   ª0A'Á'A+Á.A+úOptional[subprocess.Popen]c                ó"  — t          d| ¦  «        rt          d| › d�¦  «         dS t          d| › d�¦  «         t          dz  dz  }t          j        t
          j        t          |¦  «        d	t          | ¦  «        gt          t          ¦  «        t          j        t          j        ¬
¦  «        }t          d¦  «        D ]B}t          j        d¦  «         t          d| ¦  «        rt          d|j        › d�¦  «         |c S ŒCt          d| › d�¦  «        ‚)zIEnsures local web server is running on port 8088, launching if necessary.r   z*[QA Server] Server already active on port ú.Nz.[QA Server] Starting local web server on port z...Útoolszserve_webapp.pyz--port)ÚcwdÚstdoutÚstderré   gš™™™™™É?z.[QA Server] Server successfully started (PID: z).z)Failed to start local web server on port )r   ÚprintÚPROJECT_ROOTÚ
subprocessÚPopenÚsysÚ
executabler   ÚDEVNULLÚrangeÚtimeÚsleepÚpidÚRuntimeError)r   Úserver_scriptÚprocÚ_s       r   Úensure_server_runningr5   ^   s#  € å�K Ñ&Ô&ð ÝÐB¸4ÐBÐBÐBÑCÔCÐCØˆtÝ	Ð
D¸4Ð
DÐ
DÐ
DÑEÔEÐEÝ  7Ñ*Ð->Ñ>€MÝÔÝ	Œ�˜]Ñ+Ô+¨Xµs¸4±y´yÐAÝ•ÑÔÝÔ!ÝÔ!ð	ñ ô €Dõ �2‰YŒYð ð ˆÝŒ
�3‰ŒˆÝ˜ TÑ*Ô*ð 	ÝÐOÀ4Ä8ÐOÐOÐOÑPÔPÐPØˆKˆKˆKð	õ ÐJÀ4ÐJÐJÐJÑ
KÔ
KÐKó    TÚheadlessc                ó’   — 	 | j                              d|¬¦  «        S # t          $ r | j                              |¬¦  «        cY S w xY w)zBLaunches Chromium browser using Microsoft Edge or system Chromium.Úmsedge)Úchannelr7   )r7   )ÚchromiumÚlaunchÚ	Exception)Úplaywright_ctxr7   s     r   Úlaunch_browser_instancer?   s   sc   € ðAØÔ&×-Ò-°hÈÐ-ÑRÔRÐRøÝð Að Að AØÔ&×-Ò-°xÐ-Ñ@Ô@Ð@Ð@Ð@ðAøøøs   ‚ ž%AÁAr   c                 óH   — t                                dd¬¦  «         t           S )z=Creates the telemetry report output directory if not present.T)ÚparentsÚexist_ok)ÚTELEMETRY_DIRÚmkdir© r6   r   Úensure_telemetry_directoryrF   {   s!   € å×Ò ¨tÐÑ4Ô4Ð4ÝÐr6   ÚfilenameÚdataúDict[str, Any]c                óÔ   — t          ¦   «         }|| z  }t          |dd¬¦  «        5 }t          j        ||dd¬¦  «         ddd¦  «         n# 1 swxY w Y   t	          d|› �¦  «         |S )	z:Writes telemetry dictionary into telemetry reports folder.Úwzutf-8)Úencodingé   F)ÚindentÚensure_asciiNz[QA Telemetry] Written: )rF   ÚopenÚjsonÚdumpr&   )rG   rH   Úout_dirÚtargetÚfs        r   Úwrite_json_telemetryrV   �   s³   € å(Ñ*Ô*€GØ�xÑ€FÝ	ˆf�c GÐ	,Ñ	,Ô	,ð 9°ÝŒ	�$˜ !°%Ð8Ñ8Ô8Ð8ð9ð 9ð 9ñ 9ô 9ð 9ð 9ð 9ð 9ð 9ð 9øøøð 9ð 9ð 9ð 9å	Ð
- VÐ
-Ð
-Ñ.Ô.Ð.Ø€Ms   ¦AÁAÁA)r   r   )r   r   r   r   r   r   )r   )r   r   r   r   )T)r7   r   )r   r   )rG   r   rH   rI   r   r   )Ú__doc__Ú
__future__r   rQ   Úosr   r(   r*   r.   Úpathlibr   Útypingr   r   r   r   Ú__file__ÚresolveÚparentr'   rC   ÚINJECT_PROBE_SCRIPTr   r5   r?   rF   rV   rE   r6   r   ú<module>r`      s^  ððð ð #Ð "Ð "Ð "Ð "Ð "Ø €€€Ø 	€	€	€	Ø €€€Ø Ð Ð Ð Ø 
€
€
€
Ø €€€Ø Ð Ð Ð Ð Ð Ø -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜vÑ%¨Ñ,¨yÑ8¸;ÑF€ð@Ð ðF/ð /ð /ð /ð /ðLð Lð Lð Lð Lð*Að Að Að Að Aðð ð ð ðð ð ð ð ð r6   