UNPKG

tui-tester

Version:

End-to-end testing framework for terminal user interfaces

3,034 lines 90.5 kB
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var util = require('util');
var fs = require('fs/promises');
var child_process = require('child_process');

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n.default = e;
  return Object.freeze(n);
}

var fs__namespace = /*#__PURE__*/_interopNamespace(fs);

var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};

// node_modules/tsup/assets/cjs_shims.js
var init_cjs_shims = __esm({
  "node_modules/tsup/assets/cjs_shims.js"() {
  }
});

// src/core/utils.ts
function stripAnsi(text) {
  return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][0-9];[^\x07]*\x07/g, "").replace(/\x1b[PX^_].*?\x1b\\/g, "").replace(/\x1b[=><!~]/g, "").replace(/\x1b\[[0-9;]*[mGKHfJ]/g, "").replace(/\x1b\[[\d;]*\d*[A-Za-z]/g, "").replace(/\x1b\(\w/g, "").replace(/\x1b\[\?[\d;]*[hlc]/g, "").replace(/\x08/g, "");
}
function parseScreen(content, cols, rows) {
  const lines = content.split("\n");
  const grid = [];
  for (let y = 0; y < rows; y++) {
    grid[y] = [];
    const line = lines[y] || "";
    for (let x = 0; x < cols; x++) {
      grid[y][x] = line[x] || " ";
    }
  }
  return grid;
}
function findText(content, searchText) {
  const positions = [];
  const lines = content.split("\n");
  for (let y = 0; y < lines.length; y++) {
    let x = lines[y].indexOf(searchText);
    while (x !== -1) {
      positions.push({ x, y });
      x = lines[y].indexOf(searchText, x + 1);
    }
  }
  return positions;
}
function extractRegion(content, region) {
  const lines = content.split("\n");
  const extracted = [];
  for (let y = region.y; y < region.y + region.height && y < lines.length; y++) {
    const line = lines[y] || "";
    const start = region.x;
    const end = Math.min(region.x + region.width, line.length);
    extracted.push(line.substring(start, end));
  }
  return extracted.join("\n");
}
function normalizeText(text, options = {}) {
  let normalized = text;
  if (options.ignoreAnsi) {
    normalized = stripAnsi(normalized);
  }
  if (options.normalizeLineEndings) {
    normalized = normalized.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  }
  if (options.trimLines) {
    normalized = normalized.split("\n").map((line) => line.trim()).join("\n");
  }
  if (options.ignoreWhitespace) {
    normalized = normalized.replace(/\s+/g, " ").trim();
  }
  if (options.ignoreCase) {
    normalized = normalized.toLowerCase();
  }
  return normalized;
}
function compareScreens(actual, expected, options = {}) {
  const normalizedActual = normalizeText(actual, options);
  const normalizedExpected = normalizeText(expected, options);
  return normalizedActual === normalizedExpected;
}
function screenDiff(actual, expected) {
  const actualLines = actual.split("\n");
  const expectedLines = expected.split("\n");
  const diff = [];
  const maxLines = Math.max(actualLines.length, expectedLines.length);
  for (let i = 0; i < maxLines; i++) {
    const actualLine = actualLines[i] || "";
    const expectedLine = expectedLines[i] || "";
    if (actualLine !== expectedLine) {
      diff.push(`Line ${i + 1}:`);
      diff.push(`  Expected: "${expectedLine}"`);
      diff.push(`  Actual:   "${actualLine}"`);
    }
  }
  return diff.join("\n");
}
async function waitFor(fn, options = {}) {
  const timeout = options.timeout ?? 5e3;
  const interval = options.interval ?? 100;
  const message = options.message ?? "Timeout waiting for condition";
  const startTime = Date.now();
  while (true) {
    const result = await fn();
    if (result !== void 0) {
      return result;
    }
    if (Date.now() - startTime > timeout) {
      throw new Error(`${message} (timeout: ${timeout}ms)`);
    }
    await sleep(interval);
  }
}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function formatTimestamp(timestamp) {
  const date = new Date(timestamp);
  return date.toISOString().replace("T", " ").substring(0, 23);
}
function generateSessionName(prefix) {
  const p = prefix || "tui-test";
  const timestamp = Date.now();
  const random = Math.random().toString(36).substring(2, 8);
  return `${p}-${timestamp}-${random}`;
}
function parseScreenLines(content) {
  const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  return normalized.split("\n").map((line) => stripAnsi(line));
}
async function waitForCondition(condition, options = {}) {
  const timeout = options.timeout ?? 5e3;
  const interval = options.interval ?? 100;
  const startTime = Date.now();
  while (Date.now() - startTime < timeout) {
    const result = await condition();
    if (result) {
      return true;
    }
    await sleep(interval);
  }
  return false;
}
function escapeRegex(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function normalizeLineEndings(text) {
  return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
}
function trimScreenContent(content) {
  const isArray = Array.isArray(content);
  const lines = isArray ? content : content.split("\n");
  while (lines.length > 0 && lines[0].trim() === "") {
    lines.shift();
  }
  while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
    lines.pop();
  }
  return isArray ? lines : lines.join("\n");
}
async function isCommandAvailable(command) {
  try {
    const { exec } = await import('child_process');
    const { promisify: promisify2 } = await import('util');
    const execAsync2 = promisify2(exec);
    const cmd = command.split(" ")[0];
    const env = {
      ...process.env,
      PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env.PATH}`
    };
    await execAsync2(`which ${cmd}`, { env });
    return true;
  } catch {
    return false;
  }
}
function getTerminalSize() {
  if (typeof process !== "undefined" && process.stdout && process.stdout.isTTY) {
    return {
      cols: process.stdout.columns || 80,
      rows: process.stdout.rows || 24
    };
  }
  return { cols: 80, rows: 24 };
}
function escapeShellArg(arg) {
  return `'${arg.replace(/'/g, "'\\''")}'`;
}
function parseTmuxKey(key, modifiers) {
  const keyMap = {
    "enter": "Enter",
    "return": "Enter",
    "tab": "Tab",
    "escape": "Escape",
    "esc": "Escape",
    "space": "Space",
    "backspace": "BSpace",
    "delete": "Delete",
    "up": "Up",
    "down": "Down",
    "left": "Left",
    "right": "Right",
    "home": "Home",
    "end": "End",
    "pageup": "PageUp",
    "pagedown": "PageDown",
    "insert": "Insert",
    "f1": "F1",
    "f2": "F2",
    "f3": "F3",
    "f4": "F4",
    "f5": "F5",
    "f6": "F6",
    "f7": "F7",
    "f8": "F8",
    "f9": "F9",
    "f10": "F10",
    "f11": "F11",
    "f12": "F12"
  };
  let tmuxKey = keyMap[key.toLowerCase()] || key;
  if (modifiers) {
    const prefixes = [];
    if (modifiers.ctrl) prefixes.push("C-");
    if (modifiers.alt) prefixes.push("M-");
    if (modifiers.shift) prefixes.push("S-");
    if (prefixes.length > 0 && tmuxKey.length === 1) {
      tmuxKey = prefixes.join("") + tmuxKey.toLowerCase();
    } else if (prefixes.length > 0) {
      tmuxKey = prefixes.join("") + tmuxKey;
    }
  }
  return tmuxKey;
}
function parseTmuxMouse(x, y, button = "left") {
  const buttonMap = {
    "left": 0,
    "middle": 1,
    "right": 2
  };
  return `\x1B[<${buttonMap[button]};${x + 1};${y + 1}M`;
}
function extractCursorPosition(ansiResponse) {
  const match = ansiResponse.match(/\x1b\[(\d+);(\d+)R/);
  if (match) {
    return {
      x: parseInt(match[2]) - 1,
      // Convert from 1-based to 0-based
      y: parseInt(match[1]) - 1
    };
  }
  return null;
}
function splitLines(text) {
  return text.split(/\r?\n/);
}
function joinLines(lines, lineEnding = "\n") {
  return lines.join(lineEnding);
}
function getTextDimensions(text) {
  const lines = splitLines(text);
  const height = lines.length;
  const width = Math.max(...lines.map((line) => getStringWidth(line)));
  return { width, height };
}
function getStringWidth(str) {
  let width = 0;
  for (const char of str) {
    width += getCharWidth(char);
  }
  return width;
}
function getCharWidth(char) {
  const code = char.charCodeAt(0);
  if (code < 32 || code >= 127 && code < 160) {
    return 0;
  }
  if (code >= 4352 && code <= 4447 || // Hangul Jamo
  code >= 11904 && code <= 40959 || // CJK
  code >= 44032 && code <= 55215 || // Hangul Syllables
  code >= 63744 && code <= 64255 || // CJK Compatibility
  code >= 65072 && code <= 65103 || // CJK Compatibility Forms
  code >= 65280 && code <= 65376 || // Fullwidth Forms
  code >= 65504 && code <= 65510) {
    return 2;
  }
  return 1;
}
exports.delay = void 0;
var init_utils = __esm({
  "src/core/utils.ts"() {
    init_cjs_shims();
    exports.delay = sleep;
  }
});

// src/adapters/base.ts
exports.BaseRuntimeAdapter = void 0;
var init_base = __esm({
  "src/adapters/base.ts"() {
    init_cjs_shims();
    exports.BaseRuntimeAdapter = class {
      /**
       * Cross-platform sleep implementation
       */
      sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
      }
      /**
       * Execute command with timeout
       */
      async execWithTimeout(command, timeoutMs = 3e4) {
        const timeoutPromise = new Promise((_, reject) => {
          setTimeout(() => reject(new Error(`Command timeout: ${command}`)), timeoutMs);
        });
        return Promise.race([
          this.exec(command),
          timeoutPromise
        ]);
      }
      /**
       * Try to execute command, return null on failure
       */
      async tryExec(command) {
        try {
          return await this.exec(command);
        } catch {
          return null;
        }
      }
      /**
       * Check if command is available
       */
      async commandExists(command) {
        const result = await this.tryExec(`which ${command} 2>/dev/null`);
        return result !== null && result.code === 0;
      }
      /**
       * Get environment variable
       */
      getEnv(key) {
        if (typeof process !== "undefined") {
          return process.env[key];
        }
        if (typeof Deno !== "undefined") {
          return Deno.env.get(key);
        }
        if (typeof Bun !== "undefined") {
          return Bun.env[key];
        }
        return void 0;
      }
      /**
       * Set environment variable
       */
      setEnv(key, value) {
        if (typeof process !== "undefined") {
          process.env[key] = value;
        }
        if (typeof Deno !== "undefined") {
          Deno.env.set(key, value);
        }
        if (typeof Bun !== "undefined") {
          Bun.env[key] = value;
        }
      }
      /**
       * Get current working directory
       */
      getCwd() {
        if (typeof process !== "undefined") {
          return process.cwd();
        }
        if (typeof Deno !== "undefined") {
          return Deno.cwd();
        }
        if (typeof Bun !== "undefined") {
          return globalThis.process?.cwd() || "/";
        }
        return "/";
      }
      /**
       * Get platform
       */
      getPlatform() {
        if (typeof process !== "undefined") {
          return process.platform;
        }
        if (typeof Deno !== "undefined") {
          return Deno.build.os;
        }
        if (typeof Bun !== "undefined") {
          return globalThis.process?.platform || "unknown";
        }
        return "unknown";
      }
      /**
       * Check if running on Windows
       */
      isWindows() {
        const platform = this.getPlatform();
        return platform === "win32" || platform === "windows";
      }
      /**
       * Check if running in CI environment
       */
      isCI() {
        return !!(this.getEnv("CI") || this.getEnv("CONTINUOUS_INTEGRATION") || this.getEnv("GITHUB_ACTIONS") || this.getEnv("GITLAB_CI") || this.getEnv("CIRCLECI") || this.getEnv("TRAVIS") || this.getEnv("JENKINS_URL"));
      }
    };
  }
});

// src/adapters/bun.ts
var BunChildProcess; exports.BunAdapter = void 0;
var init_bun = __esm({
  "src/adapters/bun.ts"() {
    init_cjs_shims();
    init_base();
    BunChildProcess = class {
      process;
      // Bun.Subprocess
      _pid;
      outputBuffer = "";
      errorBuffer = "";
      constructor(command, args, options) {
        this.process = Bun.spawn([command, ...args], {
          stdin: "pipe",
          stdout: "pipe",
          stderr: "pipe",
          env: options?.env ? { ...process.env, ...options.env } : process.env,
          cwd: options?.cwd || process.cwd()
        });
        this._pid = this.process.pid;
        this.startReading().catch(() => {
        });
      }
      async startReading() {
        this.readStream(this.process.stdout, (data) => {
          this.outputBuffer += data;
        });
        this.readStream(this.process.stderr, (data) => {
          this.errorBuffer += data;
        });
      }
      async readStream(stream, callback) {
        const reader = stream.getReader();
        const decoder = new TextDecoder();
        try {
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            if (value) {
              callback(decoder.decode(value));
            }
          }
        } catch (error) {
        } finally {
          reader.releaseLock();
        }
      }
      get pid() {
        return this._pid;
      }
      get stdin() {
        return this.process.stdin;
      }
      get stdout() {
        return this.process.stdout;
      }
      get stderr() {
        return this.process.stderr;
      }
      kill(signal) {
        this.process.kill(signal);
      }
      async wait() {
        const code = await this.process.exited;
        return { code };
      }
    };
    exports.BunAdapter = class extends exports.BaseRuntimeAdapter {
      processes = /* @__PURE__ */ new Set();
      async exec(command) {
        try {
          const env = {
            ...process.env,
            PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env.PATH}`
          };
          const proc = Bun.spawn(["sh", "-c", command], {
            stdout: "pipe",
            stderr: "pipe",
            env
          });
          const stdout = await new Response(proc.stdout).text();
          const stderr = await new Response(proc.stderr).text();
          const code = await proc.exited;
          return { stdout, stderr, code };
        } catch (error) {
          return {
            stdout: "",
            stderr: error.message,
            code: 1
          };
        }
      }
      async spawn(command, args, options) {
        try {
          const proc = new BunChildProcess(command, args, options);
          this.processes.add(proc);
          proc.wait().then(() => {
            this.processes.delete(proc);
          }).catch(() => {
            this.processes.delete(proc);
          });
          return proc;
        } catch (error) {
          throw new Error(`Failed to spawn process: ${error.message}`);
        }
      }
      async kill(proc, signal) {
        try {
          if (!proc) return false;
          if (proc instanceof BunChildProcess) {
            this.processes.delete(proc);
          }
          if (typeof proc.kill === "function") {
            proc.kill(signal || "SIGTERM");
            await new Promise((resolve) => setTimeout(resolve, 100));
            if (await this.isAlive(proc)) {
              proc.kill("SIGKILL");
            }
            return true;
          }
          return false;
        } catch {
          return false;
        }
      }
      async write(proc, data) {
        try {
          if (proc && proc.stdin) {
            const stream = proc.stdin;
            const writer = stream.getWriter();
            try {
              let dataToWrite;
              if (typeof data === "string") {
                if (data === "") {
                  await writer.close();
                  return true;
                }
                const encoder = new TextEncoder();
                dataToWrite = encoder.encode(data);
              } else {
                dataToWrite = data;
              }
              await writer.write(dataToWrite);
              await writer.ready;
            } finally {
              writer.releaseLock();
            }
            return true;
          }
          return false;
        } catch (error) {
          console.error("Write error:", error);
          return false;
        }
      }
      async read(proc, timeout = 1e3) {
        if (!proc) return "";
        if (proc instanceof BunChildProcess) {
          await new Promise((resolve) => setTimeout(resolve, 200));
          const output = proc.outputBuffer;
          return output;
        }
        if (!proc.stdout) return "";
        try {
          const stream = proc.stdout;
          const reader = stream.getReader();
          const decoder = new TextDecoder();
          const timeoutPromise = new Promise((resolve) => {
            setTimeout(() => resolve(""), timeout);
          });
          const readPromise = reader.read().then(({ value, done }) => {
            reader.releaseLock();
            if (done || !value) return "";
            return decoder.decode(value);
          });
          return await Promise.race([readPromise, timeoutPromise]);
        } catch {
          return "";
        }
      }
      async resize(_proc, _cols, _rows) {
        return true;
      }
      async isAlive(proc) {
        if (!proc) return false;
        try {
          const p = proc.process;
          if (p && p.exitCode === null) {
            return true;
          }
        } catch {
        }
        return false;
      }
      async cleanup() {
        const procs = Array.from(this.processes);
        this.processes.clear();
        await Promise.all(
          procs.map(async (proc) => {
            try {
              proc.kill("SIGTERM");
              await new Promise((resolve) => setTimeout(resolve, 100));
              if (await this.isAlive(proc)) {
                proc.kill("SIGKILL");
              }
            } catch {
            }
          })
        );
      }
      async readFile(path) {
        const file = Bun.file(path);
        return await file.text();
      }
      async writeFile(path, content) {
        await Bun.write(path, content);
      }
      async exists(path) {
        const file = Bun.file(path);
        return await file.exists();
      }
      async mkdir(path, options) {
        const recursive = options?.recursive ? "-p" : "";
        await this.exec(`mkdir ${recursive} "${path}"`);
      }
      async rmdir(path, options) {
        const recursive = options?.recursive ? "-rf" : "";
        await this.exec(`rm ${recursive} "${path}"`);
      }
    };
  }
});
var execAsync, NodeChildProcess; exports.NodeAdapter = void 0;
var init_node = __esm({
  "src/adapters/node.ts"() {
    init_cjs_shims();
    init_base();
    execAsync = util.promisify(child_process.exec);
    NodeChildProcess = class {
      process;
      outputBuffer = "";
      errorBuffer = "";
      constructor(command, args, options) {
        this.process = child_process.spawn(command, args, {
          stdio: ["pipe", "pipe", "pipe"],
          env: options?.env ? { ...process.env, ...options.env } : process.env,
          cwd: options?.cwd,
          ...options?.pty ? {} : {}
        });
        if (this.process.stdout) {
          this.process.stdout.on("data", (chunk) => {
            this.outputBuffer += chunk.toString();
          });
        }
        if (this.process.stderr) {
          this.process.stderr.on("data", (chunk) => {
            this.errorBuffer += chunk.toString();
          });
        }
      }
      get output() {
        return this.outputBuffer;
      }
      get error() {
        return this.errorBuffer;
      }
      clearOutput() {
        this.outputBuffer = "";
        this.errorBuffer = "";
      }
      get pid() {
        return this.process.pid || -1;
      }
      get stdin() {
        return this.process.stdin;
      }
      get stdout() {
        return this.process.stdout;
      }
      get stderr() {
        return this.process.stderr;
      }
      kill(signal) {
        this.process.kill(signal);
      }
      wait() {
        return new Promise((resolve, reject) => {
          if (this.process.exitCode !== null) {
            resolve({ code: this.process.exitCode });
            return;
          }
          const onExit = (code) => {
            cleanup();
            resolve({ code: code || 0 });
          };
          const onError = (err) => {
            cleanup();
            reject(err);
          };
          const cleanup = () => {
            this.process.removeListener("exit", onExit);
            this.process.removeListener("error", onError);
          };
          this.process.once("exit", onExit);
          this.process.once("error", onError);
        });
      }
    };
    exports.NodeAdapter = class extends exports.BaseRuntimeAdapter {
      processes = /* @__PURE__ */ new Set();
      /**
       * Check if command is available with proper PATH
       */
      async commandExists(command) {
        try {
          const env = {
            ...process.env,
            PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env.PATH}`
          };
          const { stdout } = await execAsync(`which ${command}`, {
            encoding: "utf8",
            env
          });
          return stdout.trim().length > 0;
        } catch {
          return false;
        }
      }
      async exec(command) {
        try {
          const env = {
            ...process.env,
            PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env.PATH}`
          };
          const { stdout, stderr } = await execAsync(command, {
            encoding: "utf8",
            maxBuffer: 10 * 1024 * 1024,
            // 10MB buffer
            env
          });
          return { stdout, stderr, code: 0 };
        } catch (error) {
          return {
            stdout: error.stdout || "",
            stderr: error.stderr || error.message,
            code: error.code || 1
          };
        }
      }
      async spawn(command, args, options) {
        try {
          const proc = new NodeChildProcess(command, args, options);
          this.processes.add(proc);
          proc.wait().then(() => {
            this.processes.delete(proc);
          }).catch(() => {
            this.processes.delete(proc);
          });
          return proc;
        } catch (error) {
          throw new Error(`Failed to spawn process: ${error.message}`);
        }
      }
      async kill(proc, signal) {
        try {
          if (!proc) return false;
          if (proc instanceof NodeChildProcess) {
            this.processes.delete(proc);
          }
          if (typeof proc.kill === "function") {
            proc.kill(signal || "SIGTERM");
            await new Promise((resolve) => setTimeout(resolve, 100));
            if (await this.isAlive(proc)) {
              proc.kill("SIGKILL");
            }
            return true;
          }
          return false;
        } catch {
          return false;
        }
      }
      async write(proc, data) {
        try {
          if (proc && proc.stdin) {
            const stream = proc.stdin;
            return new Promise((resolve, reject) => {
              stream.write(data, (err) => {
                if (err) reject(err);
                else resolve(true);
              });
            });
          }
          return false;
        } catch {
          return false;
        }
      }
      async read(proc, timeout = 1e3) {
        if (!proc) return "";
        if (proc instanceof NodeChildProcess) {
          const output = proc.output;
          proc.clearOutput();
          return output;
        }
        if (!proc.stdout) return "";
        const stream = proc.stdout;
        return new Promise((resolve) => {
          let data = "";
          const handler = (chunk) => {
            data += chunk.toString();
          };
          stream.on("data", handler);
          setTimeout(() => {
            stream.off("data", handler);
            resolve(data);
          }, timeout);
        });
      }
      async resize(_proc, _cols, _rows) {
        return true;
      }
      async isAlive(proc) {
        if (!proc) return false;
        try {
          if (proc.pid > 0) {
            process.kill(proc.pid, 0);
            return true;
          }
        } catch {
        }
        return false;
      }
      async cleanup() {
        const procs = Array.from(this.processes);
        this.processes.clear();
        await Promise.all(
          procs.map(async (proc) => {
            try {
              proc.kill("SIGTERM");
              await new Promise((resolve) => setTimeout(resolve, 100));
              if (await this.isAlive(proc)) {
                proc.kill("SIGKILL");
              }
            } catch {
            }
          })
        );
      }
      async readFile(filePath) {
        return fs__namespace.readFile(filePath, "utf8");
      }
      async writeFile(filePath, content) {
        await fs__namespace.writeFile(filePath, content, "utf8");
      }
      async exists(filePath) {
        try {
          await fs__namespace.access(filePath);
          return true;
        } catch {
          return false;
        }
      }
      async mkdir(dirPath, options) {
        await fs__namespace.mkdir(dirPath, options);
      }
      async rmdir(dirPath, options) {
        await fs__namespace.rm(dirPath, { recursive: options?.recursive, force: true });
      }
    };
  }
});

// src/adapters/deno.ts
var DenoChildProcess; exports.DenoAdapter = void 0;
var init_deno = __esm({
  "src/adapters/deno.ts"() {
    init_cjs_shims();
    init_base();
    DenoChildProcess = class {
      process;
      // Deno.ChildProcess
      _stdin;
      _stdout;
      _stderr;
      outputBuffer = "";
      errorBuffer = "";
      constructor(command, args, options) {
        const currentEnv = globalThis.Deno?.env?.toObject?.() || (typeof process !== "undefined" ? process.env : {});
        this.process = new Deno.Command(command, {
          args,
          stdin: "piped",
          stdout: "piped",
          stderr: "piped",
          env: options?.env ? { ...currentEnv, ...options.env } : currentEnv,
          cwd: options?.cwd
        }).spawn();
        this._stdin = this.process.stdin;
        this._stdout = this.process.stdout;
        this._stderr = this.process.stderr;
        this.startReading();
      }
      async startReading() {
        this.readStream(this._stdout, (data) => {
          this.outputBuffer += data;
        });
        this.readStream(this._stderr, (data) => {
          this.errorBuffer += data;
        });
      }
      async readStream(stream, callback) {
        const reader = stream.getReader();
        const decoder = new TextDecoder();
        try {
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            if (value) {
              callback(decoder.decode(value));
            }
          }
        } catch (error) {
        } finally {
          reader.releaseLock();
        }
      }
      get pid() {
        return this.process.pid;
      }
      get stdin() {
        return this._stdin;
      }
      get stdout() {
        return this._stdout;
      }
      get stderr() {
        return this._stderr;
      }
      kill(signal) {
        this.process.kill(signal);
      }
      async wait() {
        const status = await this.process.status;
        return { code: status.code };
      }
    };
    exports.DenoAdapter = class extends exports.BaseRuntimeAdapter {
      processes = /* @__PURE__ */ new Set();
      async exec(command) {
        try {
          const currentEnv = globalThis.Deno?.env?.toObject?.() || (typeof process !== "undefined" ? process.env : {});
          const currentPath = globalThis.Deno?.env?.get?.("PATH") || (typeof process !== "undefined" ? process.env.PATH : "");
          const extendedEnv = {
            ...currentEnv,
            PATH: `/opt/homebrew/bin:/usr/local/bin:${currentPath}`
          };
          const cmd = new Deno.Command("sh", {
            args: ["-c", command],
            stdout: "piped",
            stderr: "piped",
            env: extendedEnv
          });
          const { code, stdout, stderr } = await cmd.output();
          return {
            stdout: new TextDecoder().decode(stdout),
            stderr: new TextDecoder().decode(stderr),
            code
          };
        } catch (error) {
          return {
            stdout: "",
            stderr: error.message,
            code: 1
          };
        }
      }
      async spawn(command, args, options) {
        try {
          const proc = new DenoChildProcess(command, args, options);
          this.processes.add(proc);
          proc.wait().then(() => {
            this.processes.delete(proc);
          }).catch(() => {
            this.processes.delete(proc);
          });
          return proc;
        } catch (error) {
          throw new Error(`Failed to spawn process: ${error.message}`);
        }
      }
      async kill(proc, signal) {
        try {
          if (!proc) return false;
          if (proc instanceof DenoChildProcess) {
            this.processes.delete(proc);
          }
          if (typeof proc.kill === "function") {
            proc.kill(signal || "SIGTERM");
            await new Promise((resolve) => setTimeout(resolve, 100));
            if (await this.isAlive(proc)) {
              proc.kill("SIGKILL");
            }
            return true;
          }
          return false;
        } catch {
          return false;
        }
      }
      async write(proc, data) {
        try {
          if (proc && proc.stdin) {
            const stream = proc.stdin;
            const writer = stream.getWriter();
            const encoder = new TextEncoder();
            await writer.write(encoder.encode(data));
            writer.releaseLock();
            return true;
          }
          return false;
        } catch {
          return false;
        }
      }
      async read(proc, timeout = 1e3) {
        if (!proc) return "";
        if (proc instanceof DenoChildProcess) {
          await new Promise((resolve) => setTimeout(resolve, 50));
          const output = proc.outputBuffer;
          proc.outputBuffer = "";
          return output;
        }
        if (!proc.stdout) return "";
        try {
          const stream = proc.stdout;
          const reader = stream.getReader();
          const decoder = new TextDecoder();
          const timeoutPromise = new Promise((resolve) => {
            setTimeout(() => resolve(""), timeout);
          });
          const readPromise = reader.read().then(({ value, done }) => {
            reader.releaseLock();
            if (done || !value) return "";
            return decoder.decode(value);
          });
          return await Promise.race([readPromise, timeoutPromise]);
        } catch {
          return "";
        }
      }
      async resize(_proc, _cols, _rows) {
        return true;
      }
      async isAlive(proc) {
        if (!proc) return false;
        try {
          const p = proc.process;
          if (p && typeof p.status === "function") {
            return true;
          }
        } catch {
        }
        return false;
      }
      async cleanup() {
        const procs = Array.from(this.processes);
        this.processes.clear();
        await Promise.all(
          procs.map(async (proc) => {
            try {
              proc.kill("SIGTERM");
              await new Promise((resolve) => setTimeout(resolve, 100));
              if (await this.isAlive(proc)) {
                proc.kill("SIGKILL");
              }
            } catch {
            }
          })
        );
      }
      async readFile(path) {
        return await Deno.readTextFile(path);
      }
      async writeFile(path, content) {
        await Deno.writeTextFile(path, content);
      }
      async exists(path) {
        try {
          await Deno.stat(path);
          return true;
        } catch {
          return false;
        }
      }
      async mkdir(path, options) {
        await Deno.mkdir(path, options);
      }
      async rmdir(path, options) {
        await Deno.remove(path, { recursive: options?.recursive });
      }
    };
  }
});

// src/adapters/index.ts
function registerAdapter(name, adapterClass, options) {
  adapterRegistry.set(name.toLowerCase(), {
    adapterClass,
    detect: options?.detect,
    priority: options?.priority ?? 0
  });
}
function setDefaultAdapter(name) {
  defaultAdapterName = name.toLowerCase();
}
function detectRuntime() {
  if (typeof process !== "undefined" && process.env?.TUI_TESTER_ADAPTER) {
    const envAdapter = process.env.TUI_TESTER_ADAPTER.toLowerCase();
    if (envAdapter === "node" || envAdapter === "deno" || envAdapter === "bun") {
      return envAdapter;
    }
  }
  if (typeof Deno !== "undefined" && typeof Deno.version !== "undefined") {
    return "deno";
  }
  if (typeof Bun !== "undefined" && typeof Bun.version !== "undefined") {
    return "bun";
  }
  if (typeof process !== "undefined" && process.versions?.node) {
    return "node";
  }
  return "node";
}
function createAdapter(runtime) {
  if (defaultAdapterName) {
    const customAdapter = adapterRegistry.get(defaultAdapterName);
    if (customAdapter) {
      return new customAdapter.adapterClass();
    }
    if (defaultAdapterName === "node" || defaultAdapterName === "deno" || defaultAdapterName === "bun") {
      runtime = defaultAdapterName;
    }
  }
  if (!runtime) {
    const sortedAdapters = Array.from(adapterRegistry.entries()).filter(([, config]) => config.detect).sort(([, a], [, b]) => (b.priority ?? 0) - (a.priority ?? 0));
    for (const [, config] of sortedAdapters) {
      if (config.detect && config.detect()) {
        return new config.adapterClass();
      }
    }
  }
  if (typeof runtime === "string" && adapterRegistry.has(runtime.toLowerCase())) {
    const customAdapter = adapterRegistry.get(runtime.toLowerCase());
    return new customAdapter.adapterClass();
  }
  const detectedRuntime = typeof runtime === "string" ? runtime : runtime || detectRuntime();
  switch (detectedRuntime) {
    case "node":
      return new exports.NodeAdapter();
    case "deno":
      return new exports.DenoAdapter();
    case "bun":
      return new exports.BunAdapter();
    default:
      return new exports.NodeAdapter();
  }
}
function getAdapter() {
  if (!currentAdapter) {
    currentAdapter = createAdapter();
  }
  return currentAdapter;
}
function setAdapter(adapter) {
  currentAdapter = adapter;
}
function resetAdapter() {
  currentAdapter = null;
}
var adapterRegistry, defaultAdapterName, currentAdapter;
var init_adapters = __esm({
  "src/adapters/index.ts"() {
    init_cjs_shims();
    init_bun();
    init_node();
    init_deno();
    init_base();
    adapterRegistry = /* @__PURE__ */ new Map();
    defaultAdapterName = null;
    currentAdapter = null;
  }
});

// src/snapshot/snapshot-manager.ts
var snapshot_manager_exports = {};
__export(snapshot_manager_exports, {
  SnapshotManager: () => exports.SnapshotManager,
  getSnapshotManager: () => getSnapshotManager,
  resetSnapshotManager: () => resetSnapshotManager
});
function getSnapshotManager(options) {
  if (!globalSnapshotManager) {
    globalSnapshotManager = new exports.SnapshotManager(options);
  } else if (options) {
    globalSnapshotManager.configure(options);
  }
  return globalSnapshotManager;
}
function resetSnapshotManager() {
  globalSnapshotManager = null;
}
exports.SnapshotManager = void 0; var globalSnapshotManager;
var init_snapshot_manager = __esm({
  "src/snapshot/snapshot-manager.ts"() {
    init_cjs_shims();
    init_adapters();
    init_utils();
    exports.SnapshotManager = class {
      adapter;
      options;
      snapshots = /* @__PURE__ */ new Map();
      snapshotCounter = 0;
      snapshotDir;
      constructor(snapshotDir) {
        this.adapter = getAdapter();
        if (typeof snapshotDir === "string") {
          this.snapshotDir = snapshotDir;
          this.options = {
            updateSnapshots: false,
            snapshotDir,
            diffOptions: {},
            format: "text"
          };
        } else {
          const options = snapshotDir || {};
          this.snapshotDir = options.snapshotDir ?? "./__snapshots__";
          this.options = {
            updateSnapshots: options.updateSnapshots ?? false,
            snapshotDir: this.snapshotDir,
            diffOptions: options.diffOptions ?? {},
            format: options.format ?? "text"
          };
        }
      }
      /**
       * Configure the snapshot manager
       */
      configure(options) {
        if (options.updateSnapshots !== void 0) {
          this.options.updateSnapshots = options.updateSnapshots;
        }
        if (options.snapshotDir !== void 0) {
          this.options.snapshotDir = options.snapshotDir;
          this.snapshotDir = options.snapshotDir;
        }
        if (options.diffOptions !== void 0) {
          this.options.diffOptions = options.diffOptions;
        }
        if (options.format !== void 0) {
          this.options.format = options.format;
        }
        if (options.stripAnsi !== void 0 && this.options.diffOptions) {
          this.options.diffOptions.ignoreAnsi = options.stripAnsi;
        }
        if (options.trim !== void 0 && this.options.diffOptions) {
          this.options.diffOptions.ignoreWhitespace = options.trim;
        }
      }
      /**
       * Create a snapshot from screen capture
       */
      createSnapshot(capture, name) {
        const snapshotName = name || `snapshot-${++this.snapshotCounter}`;
        const snapshot = {
          id: this.generateSnapshotId(snapshotName),
          name: snapshotName,
          capture,
          metadata: {
            createdAt: Date.now(),
            format: this.options.format
          }
        };
        this.snapshots.set(snapshot.id, snapshot);
        return snapshot;
      }
      /**
       * Match a capture against a snapshot
       */
      async matchSnapshot(capture, snapshotName, testPath) {
        const snapshotPath = this.getSnapshotPath(snapshotName, testPath);
        try {
          const existingSnapshot = await this.loadSnapshot(snapshotPath);
          const pass = this.compareCaptures(capture, existingSnapshot.capture);
          if (!pass) {
            const diff = this.generateDiff(capture, existingSnapshot.capture);
            return {
              pass: false,
              message: `Snapshot mismatch for "${snapshotName}"`,
              diff
            };
          }
          return { pass: true };
        } catch (error) {
          if (this.options.updateSnapshots) {
            const snapshot = this.createSnapshot(capture, snapshotName);
            await this.saveSnapshot(snapshot, snapshotPath);
            return {
              pass: true,
              message: `New snapshot created for "${snapshotName}"`
            };
          } else {
            return {
              pass: false,
              message: `Snapshot "${snapshotName}" does not exist. Run with updateSnapshots=true to create it.`
            };
          }
        }
      }
      /**
       * Compare two screen captures
       */
      compareCaptures(actual, expected) {
        switch (this.options.format) {
          case "ansi":
            return compareScreens(actual.raw, expected.raw, this.options.diffOptions);
          case "text":
            return compareScreens(actual.text, expected.text, this.options.diffOptions);
          case "json":
          default: {
            const actualNorm = normalizeText(actual.text, this.options.diffOptions);
            const expectedNorm = normalizeText(expected.text, this.options.diffOptions);
            return actualNorm === expectedNorm;
          }
        }
      }
      /**
       * Generate diff between captures
       */
      generateDiff(actual, expected) {
        const actualText = this.options.format === "ansi" ? actual.raw : actual.text;
        const expectedText = this.options.format === "ansi" ? expected.raw : expected.text;
        return screenDiff(actualText, expectedText);
      }
      /**
       * Save snapshot to file
       */
      async saveSnapshot(snapshot, path) {
        const snapshotPath = path || this.getSnapshotPath(snapshot.name);
        const dir = snapshotPath.substring(0, snapshotPath.lastIndexOf("/"));
        await this.adapter.mkdir(dir, { recursive: true });
        let content;
        switch (this.options.format) {
          case "text":
            content = snapshot.capture.text;
            break;
          case "ansi":
            content = snapshot.capture.raw;
            break;
          case "json":
          default:
            content = JSON.stringify(snapshot, null, 2);
            break;
        }
        await this.adapter.writeFile(snapshotPath, content);
      }
      /**
       * Load snapshot from file
       */
      async loadSnapshot(path) {
        const content = await this.adapter.readFile(path);
        let snapshot;
        switch (this.options.format) {
          case "text":
          case "ansi":
            const lines = content.split("\n");
            snapshot = {
              id: path,
              name: path.substring(path.lastIndexOf("/") + 1),
              capture: {
                raw: this.options.format === "ansi" ? content : "",
                text: this.options.format === "text" ? content : "",
                lines,
                timestamp: 0,
                size: { cols: 0, rows: lines.length }
              }
            };
            break;
          case "json":
          default:
            snapshot = JSON.parse(content);
            break;
        }
        this.snapshots.set(snapshot.id, snapshot);
        return snapshot;
      }
      /**
       * Update existing snapshot
       */
      async updateSnapshot(snapshotName, capture, testPath) {
        const snapshot = this.createSnapshot(capture, snapshotName);
        const snapshotPath = this.getSnapshotPath(snapshotName, testPath);
        await this.saveSnapshot(snapshot, snapshotPath);
      }
      /**
       * Remove snapshot
       */
      async removeSnapshot(snapshotName, testPath) {
        const snapshotPath = this.getSnapshotPath(snapshotName, testPath);
        const snapshot = Array.from(this.snapshots.values()).find((s) => s.name === snapshotName);
        if (snapshot) {
          this.snapshots.delete(snapshot.id);
        }
        if (await this.adapter.exists(snapshotPath)) {
          await this.adapter.exec(`rm "${snapshotPath}"`);
        }
      }
      /**
       * List all snapshots
       */
      async listSnapshots() {
        const extension = this.getFileExtension();
        const result = await this.adapter.exec(
          `find "${this.options.snapshotDir}" -name "*${extension}" 2>/dev/null || true`
        );
        return result.stdout.split("\n").filter((line) => line.trim()).map((path) => path.substring(path.lastIndexOf("/") + 1));
      }
      /**
       * Clear all snapshots
       */
      async clearSnapshots() {
        this.snapshots.clear();
        if (await this.adapter.exists(this.options.snapshotDir)) {
          await this.adapter.exec(`rm -rf "${this.options.snapshotDir}"`);
        }
      }
      /**
       * Get snapshot by name
       */
      getSnapshot(name) {
        return Array.from(this.snapshots.values()).find((s) => s.name === name);
      }
      /**
       * Check if snapshot exists
       */
      async snapshotExists(snapshotName, testPath) {
        const snapshotPath = this.getSnapshotPath(snapshotName, testPath);
        return this.adapter.exists(snapshotPath);
      }
      // Simple API methods for backward compatibility
      /**
       * Save a simple text snapshot
       */
      async save(name, content) {
        const sanitizedName = this.sanitizeName(name);
        const filePath = `${this.snapshotDir}/${sanitizedName}.snap`;
        await this.adapter.mkdir(this.snapshotDir, { recursive: true });
        await this.adapter.writeFile(filePath, content);
      }
      /**
       * Load a simple text snapshot
       */
      async load(name) {
        try {
          const sanitizedName = this.sanitizeName(name);
          const filePath = `${this.snapshotDir}/${sanitizedName}.snap`;
          return await this.adapter.readFile(filePath);
        } catch (error) {
          return null;
        }
      }
      /**
       * Compare two snapshots
       */
      async compare(name1, name2) {
        const content1 = await this.load(name1);
        const content2 = await this.load(name2);
        if (content1 === null || content2 === null) {
          return {
            identical: false,
            differences: [],
            error: `Snapshot does not exist: ${content1 === null ? name1 : name2}`
          };
        }
        const lines1 = content1.split("\n");
        const lines2 = content2.split("\n");
        const differences = [];
        const maxLines = Math.max(lines1.length, lines2.length);
        for (let i = 0; i < maxLines; i++) {
          const line1 = lines1[i];
          const line2 = lines2[i];
          if (line1 !== line2) {
            differences.push({
              line: i + 1,
              expected: line1,
              actual: line2
            });
          }
        }
        return {
          identical: differences.length === 0,
          differences
        };
      }
      /**
       * Compare with options
       */
      async compareWithOptions(name1, name2, options) {
        const content1 = await this.load(name1);
        const content2 = await this.load(name2);
        if (content1 === null || content2 === null) {
          return { identical: false, match: false, diff: `Snapshot does not exist: ${content1 === null ? name1 : name2}` };
        }
        let normalized1 = content1;
        let normalized2 = content2;
        if (options.ignoreWhitespace) {
          normalized1 = normalized1.replace(/\s+/g, " ").trim();
          normalized2 = normalized2.replace(/\s+/g, " ").trim();
        }
        if (options.ignoreAnsi) {
          const ansiRegex = /\x1b\[[0-9;]*m/g;
          normalized1 = normalized1.replace(ansiRegex, "");
          normalized2 = normalized2.replace(ansiRegex, "");
        }
        const match = normalized1 === normalized2;
        if (!match) {
          const diff = this.createDiff(normalized2, normalized1);
          return { identical: false, match: false, diff };
        }
        return { identical: true, match: true };
      }
      /**
       * Delete a snapshot
       */
      async delete(name) {
        try {
          const sanitizedName = this.sanitizeName(name);
          const filePath = `${this.snapshotDir}/${sanitizedName}.snap`;
          if (await this.adapter.exists(filePath)) {
            await this.adapter.exec(`rm "${filePath}"`);
            return true;
          }
          return false;
        } catch (error) {
          return false;
        }
      }
      /**
       * Delete all snapshots
       */
      async deleteAll() {
        try {
          if (await this.adapter.exists(this.snapshotDir)) {
            await this.adapter.exec(`rm -rf "${this.snapshotDir}"`);
            await this.adapter.mkdir(this.snapshotDir, { recursive: true });
          }
        } catch (error) {
        }
      }
      /**
       * List all snapshot names
       */
      async list(pattern) {
        try {
          if (!await this.adapter.exists(this.snapshotDir)) {
            return [];
          }
          const result = await this.adapter.exec(
            `find "${this.snapshotDir}" -name "*.snap" 2>/dev/null || true`
          );
          const files = result.stdout.split("\n").filter((line) => line.trim() && line.endsWith(".snap")).map((path) => {
            const filename = path.substring(path.lastIndexOf("/") + 1);
            return filename.replace(".snap", "");
          });
          if (pattern) {
            const regex = new RegExp(pattern);
            return files.filter((name) => regex.test(name));
          }
          return files;
        } catch (error) {
          return [];
        }
      }
      /**
       * List by pattern (alias for list with pattern)
       */
      async listByPattern(pattern) {
        return this.list(pattern);
      }
      /**
       * Check if update is needed
       */
      async needsUpdate(name, content) {
        const existing = await this.load(name);
        if (existing === null) {
          return true;
        }
        return existing !== content;
      }
      /**
       * Save with metadata
       */
      async saveWithMetadata(name, content, metadata) {
        const data = JSON.stringify({
          content,
          metadata: {
            ...metadata,
            timestamp: Date.now()
          }
        }, null, 2);
        const sanitizedName = this.sanitizeName(name);
        const filePath = `${this.snapshotDir}/${sanitizedName}.snap`;
        await this.adapter.mkdir(this.snapshotDir, { recursive: true });
        await this.adapter.writeFile(filePath, data);
      }
      /**
       * Load with metadata
       */
      async loadWithMetadata(name) {
        try {
          const sanitizedName = this.sanitizeName(name);
          const filePath = `${this.snapshotDir}/${sanitizedName}.snap`;
          const data = await this.adapter.readFile(filePath);
          try {
            const parsed = JSON.parse(data);
            if (parsed.content !== void 0) {
              return parsed;
            }
            return { content: data };
          } catch {
            return { content: data };
          }
        } catch (error) {
          return null;
        }
      }
      /**
       * Format snapshot for display
       */
      async format(name) {
        const content = await this.load(name);
        if (!content) return null;
        const lines = content.split("\n");
        return lines.map((line, i) => `${i + 1}: ${line}`).join("\n");
      }
      /**
       * Format with options
       */
      async formatWithOptions(name, options) {
        const content = await this.load(name);
        if (!content) return null;
        const lines = content.split("\n");
        if (options.lineNumbers) {
          return lines.map((line, i) => `${(i + 1).toString().padStart(3, " ")} | ${line}`).join("\n");
        }
        return content;
      }
      /**
       * Generate unified diff between two snapshots
       */
      async diff(name1, name2) {
        const content1 = await this.load(name1);
        const content2 = await this.load(name2);
        if (!content1 || !content2) {
          return null;
        }
        return this.createDiff(content1, content2);
      }
      /**
       * Sanitize snapshot name
       */
      sanitizeName(name) {
        return name.replace(/\//g, "_slash_").replace(/\.\./g, "_dot_").replace(/\s+/g, "_space_").replace(/[^a-zA-Z0-9-_]/g, "");
      }
      /**
       * Create a simple diff
       */
      createDiff(expected, actual) {
        const expectedLines = expected.split("\n");
        const actualLines = actual.split("\n");
        const maxLines = Math.max(expectedLines.length, actualLines.length);
        const diff = [];
        for (let i = 0; i < maxLines; i++) {
          const expectedLine = expectedLines[i];
          const actualLine = actualLines[i];
          if (expectedLine !== actualLine) {
            if (expectedLine !== void 0) {
              diff.push(`-${expectedLine}`);
            }
            if (actualLine !== void 0) {
              diff.push(`+${actualLine}`);
            }
          }
        }
        return diff.join("\n");
      }
      // Private helper methods
      generateSnapshotId(name) {
        return `${name}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
      }
      getSnapshotPath(snapshotName, testPath) {
        const extension = this.getFileExtension();
        if (testPath) {
          const testDir = testPath.substring(0, testPath.lastIndexOf("/"));
          const testFile = testPath.substring(testPath.lastIndexOf("/") + 1);
          const testName = testFile.replace(/\.(test|spec)\.(ts|js)$/, "");
          return `${testDir}/__snapshots__/${testName}.${snapshotName}${extension}`;
        } else {
          return `${this.options.snapshotDir}/${snapshotName}${extension}`;
        }
      }
      getFileExtension() {
        switch (this.options.format) {
          case "text":
            return ".txt";
          case "ansi":
            return ".ansi";
          case "json":
          default:
            return ".json";
        }
      }
    };
    globalSnapshotManager = null;
  }
});

// src/index.ts
init_cjs_shims();

// src/core/types.ts
init_cjs_shims();

// src/index.ts
init_utils();

// src/tmux-tester.ts
init_cjs_shims();
init_adapters();
init_utils();
var TmuxTester = class {
  config;
  adapter;
  sessionName;
  running = false;
  _outputBuffer = "";
  // Store last captured output
  recording = null;
  snapshots = /* @__PURE__ */ new Map();
  debugMode;
  constructor(config) {
    this.config = {
      command: config.command,
      size: config.size || { cols: 80, rows: 24 },
      env: config.env || {},
      cwd: config.cwd || process.cwd(),
      shell: config.shell || "sh",
      sessionName: config.sessionName || generateSessionName(),
      debug: config.debug || false,
      recordingEnabled: config.recordingEnabled || false,
      snapshotDir: config.snapshotDir || "./snapshots"
    };
    this.adapter = getAdapter();
    this.sessionName = this.config.sessionName;
    this.debugMode = this.config.debug;
  }
  /**
   * Start the tmux session and launch the application
   */
  async start() {
    if (this.running) {
      throw new Error("Tester is already running");
    }
    const tmuxAvailable = this.adapter.commandExists ? await this.adapter.commandExists("tmux") : true;
    if (!tmuxAvailable) {
      throw new Error("tmux is not installed. Please install tmux to use the terminal tester.");
    }
    if (this.adapter.tryExec) {
      await this.adapter.tryExec(`tmux kill-session -t ${this.sessionName} 2>/dev/null`);
    }
    const { cols, rows } = this.config.size;
    const envVars = Object.entries(this.config.env).map(([key, value]) => `-e ${key}=${escapeShellArg(value)}`).join(" ");
    const needsShell = this.config.command.length > 0 && !this.config.command[0].match(/^(bash|sh|zsh|fish|dash)$/);
    let createCmd;
    if (needsShell) {
      createCmd = `tmux new-session -d -s ${this.sessionName} -x ${cols} -y ${rows} ${envVars} -c ${escapeShellArg(this.config.cwd)} bash`;
    } else {
      const shell = this.config.command[0] || "bash";
      createCmd = `tmux new-session -d -s ${this.sessionName} -x ${cols} -y ${rows} ${envVars} -c ${escapeShellArg(this.config.cwd)} ${shell}`;
    }
    this.debug(`Creating tmux session: ${createCmd}`);
    const result = await this.adapter.exec(createCmd);
    if (result.code !== 0) {
      throw new Error(`Failed to create tmux session: ${result.stderr}`);
    }
    this.running = true;
    if (needsShell) {
      await this.sleep(1e3);
      const appCmd = this.config.command.join(" ");
      await this.sendCommand(appCmd);
    }
    await this.sleep(500);
    this.debug("Tmux session started successfully");
    if (this.config.recordingEnabled) {
      this.startRecording();
    }
  }
  /**
   * Stop the tmux session
   */
  async stop() {
    if (!this.running) {
      return;
    }
    if (this.recording) {
      this.stopRecording();
    }
    const result = await this.adapter.exec(`tmux kill-session -t ${this.sessionName}`);
    if (result.code !== 0) {
      this.debug(`Warning: Failed to kill tmux session: ${result.stderr}`);
    }
    this.running = false;
    this.debug("Tmux session stopped");
  }
  /**
   * Restart the session
   */
  async restart() {
    await this.stop();
    await this.start();
  }
  /**
   * Check if the session is running
   */
  isRunning() {
    return this.running;
  }
  /**
   * Send text to the terminal
   */
  async sendText(text) {
    this.ensureRunning();
    const escaped = escapeShellArg(text);
    const cmd = `tmux send-keys -t ${this.sessionName} ${escaped}`;
    this.debug(`Sending text: ${text}`);
    await this.adapter.exec(cmd);
    this.recordEvent("input", { type: "text", text });
    await this.sleep(100);
  }
  /**
   * Send a key with optional modifiers
   */
  async sendKey(key, modifiers) {
    this.ensureRunning();
    const tmuxKey = parseTmuxKey(key, modifiers);
    const cmd = `tmux send-keys -t ${this.sessionName} ${tmuxKey}`;
    this.debug(`Sending key: ${key} (tmux: ${tmuxKey})`);
    await this.adapter.exec(cmd);
    this.recordEvent("key", { key, modifiers });
    await this.sleep(100);
  }
  /**
   * Send multiple keys
   */
  async sendKeys(keys) {
    for (const key of keys) {
      await this.sendKey(key);
    }
  }
  /**
   * Enable mouse support in the terminal
   */
  async enableMouse() {
    this.ensureRunning();
    await this.adapter.exec(`tmux set -t ${this.sessionName} mouse on`);
    this.debug("Mouse support enabled");
  }
  /**
   * Disable mouse support in the terminal
   */
  async disableMouse() {
    this.ensureRunning();
    await this.adapter.exec(`tmux set -t ${this.sessionName} mouse off`);
    this.debug("Mouse support disabled");
  }
  /**
   * Click at specific position
   */
  async click(x, y) {
    await this.sendMouse({
      type: "click",
      position: { x, y },
      button: "left"
    });
  }
  /**
   * Click on text in the terminal
   */
  async clickText(text) {
    const capture = await this.captureScreen();
    const lines = capture.lines;
    for (let y = 0; y < lines.length; y++) {
      const x = lines[y].indexOf(text);
      if (x !== -1) {
        await this.click(x, y);
        return;
      }
    }
    throw new Error(`Text "${text}" not found on screen`);
  }
  /**
   * Double click at specific position
   */
  async doubleClick(x, y) {
    await this.click(x, y);
    await this.sleep(50);
    await this.click(x, y);
  }
  /**
   * Right click at specific position
   */
  async rightClick(x, y) {
    await this.sendMouse({
      type: "click",
      position: { x, y },
      button: "right"
    });
  }
  /**
   * Drag from one position to another
   */
  async drag(from, to) {
    await this.sendMouse({
      type: "click",
      position: from,
      button: "left"
    });
    await this.sendMouse({
      type: "move",
      position: to,
      button: "left"
    });
    await this.sendMouse({
      type: "click",
      position: to,
      button: "left"
    });
  }
  /**
   * Scroll up or down
   */
  async scroll(direction, lines = 1) {
    const currentPos = await this.getCursor();
    for (let i = 0; i < lines; i++) {
      await this.sendMouse({
        type: "scroll",
        position: currentPos,
        button: direction
      });
      await this.sleep(50);
    }
  }
  /**
   * Send a mouse event
   */
  async sendMouse(event) {
    this.ensureRunning();
    await this.adapter.exec(`tmux set -t ${this.sessionName} mouse on`);
    let cmd;
    switch (event.type) {
      case "click":
        const button = event.button === "left" || event.button === "middle" || event.button === "right" ? event.button : "left";
        const sequence = parseTmuxMouse(event.position.x, event.position.y, button);
        cmd = `tmux send-keys -t ${this.sessionName} -H ${sequence}`;
        break;
      case "move":
        cmd = `tmux send-keys -t ${this.sessionName} -H '\x1B[<35;${event.position.x + 1};${event.position.y + 1}M'`;
        break;
      case "scroll":
        const scrollButton = event.button === "up" ? 4 : 5;
        cmd = `tmux send-keys -t ${this.sessionName} -H '\x1B[<${scrollButton};${event.position.x + 1};${event.position.y + 1}M'`;
        break;
      default:
        this.debug(`Unsupported mouse event type: ${event.type}`);
        return;
    }
    this.debug(`Sending mouse event: ${event.type} at (${event.position.x}, ${event.position.y})`);
    await this.adapter.exec(cmd);
    this.recordEvent("mouse", event);
    await this.sleep(100);
  }
  /**
   * Paste text (using bracketed paste mode)
   */
  async paste(text) {
    this.ensureRunning();
    await this.adapter.exec(`tmux send-keys -t ${this.sessionName} '\x1B[200~'`);
    await this.sendText(text);
    await this.adapter.exec(`tmux send-keys -t ${this.sessionName} '\x1B[201~'`);
    this.recordEvent("input", { type: "paste", text });
  }
  /**
   * Type text with optional delay between characters
   */
  async typeText(text, delayMs = 50) {
    for (const char of text) {
      await this.sendText(char);
      await this.sleep(delayMs);
    }
  }
  /**
   * Capture the current screen
   */
  async captureScreen() {
    this.ensureRunning();
    const result = await this.adapter.exec(`tmux capture-pane -t ${this.sessionName} -p -e`);
    if (result.code !== 0) {
      throw new Error(`Failed to capture screen: ${result.stderr}`);
    }
    const raw = result.stdout;
    const text = stripAnsi(raw);
    const lines = splitLines(text);
    const capture = {
      raw,
      text,
      lines,
      timestamp: Date.now(),
      size: this.config.size
    };
    this._outputBuffer = raw;
    if (this.recording) {
      this.recording.captures.push(capture);
    }
    return capture;
  }
  /**
   * Get screen text without ANSI codes
   */
  async getScreenText() {
    const capture = await this.captureScreen();
    return capture.text;
  }
  /**
   * Get screen lines without ANSI codes
   */
  async getScreenLines() {
    const capture = await this.captureScreen();
    return capture.lines;
  }
  /**
   * Get screen content (alias for getScreenText for backward compatibility)
   */
  async getScreenContent() {
    return this.getScreenText();
  }
  /**
   * Get screen content with options
   */
  async getScreen(options) {
    if (options?.stripAnsi) {
      return this.getScreenText();
    }
    const capture = await this.captureScreen();
    return capture.raw;
  }
  /**
   * Get screen lines (alias for getScreenLines)
   */
  async getLines() {
    return this.getScreenLines();
  }
  /**
   * Wait for text to appear on screen
   */
  async waitForText(text, options) {
    await waitFor(
      async () => {
        const screenText = await this.getScreenText();
        return screenText.includes(text) ? true : void 0;
      },
      {
        timeout: options?.timeout ?? 5e3,
        interval: options?.interval ?? 100,
        message: options?.message ?? `Text "${text}" not found`
      }
    );
  }
  /**
   * Wait for condition to be true
   */
  async waitFor(predicate, options) {
    await waitFor(
      async () => {
        const screenText = await this.getScreenText();
        const result = await predicate(screenText);
        return result ? true : void 0;
      },
      {
        timeout: options?.timeout ?? 5e3,
        interval: options?.interval ?? 100,
        message: options?.message ?? `Condition not met`
      }
    );
  }
  /**
   * Wait for pattern to match screen content
   */
  async waitForPattern(pattern, options) {
    await waitFor(
      async () => {
        const screenText = await this.getScreenText();
        return pattern.test(screenText) ? true : void 0;
      },
      {
        timeout: options?.timeout ?? 5e3,
        interval: options?.interval ?? 100,
        message: options?.message ?? `Pattern ${pattern} not matched`
      }
    );
  }
  /**
   * Wait for specific line to contain text
   */
  async waitForLine(lineNumber, text, options) {
    await waitFor(
      async () => {
        const lines = await this.getScreenLines();
        const line = lines[lineNumber];
        return line && line.includes(text) ? true : void 0;
      },
      {
        timeout: options?.timeout ?? 5e3,
        interval: options?.interval ?? 100,
        message: options?.message ?? `Line ${lineNumber} does not contain "${text}"`
      }
    );
  }
  /**
   * Assert specific line contains text
   */
  async assertLine(lineNumber, predicate) {
    const lines = await this.getScreenLines();
    const line = lines[lineNumber];
    if (!line) {
      throw new Error(`Line ${lineNumber} does not exist. Screen has ${lines.length} lines.`);
    }
    if (typeof predicate === "string") {
      if (!line.includes(predicate)) {
        throw new Error(`Line ${lineNumber} does not contain "${predicate}". Line content: "${line}"`);
      }
    } else {
      if (!predicate(line)) {
        throw new Error(`Line ${lineNumber} assertion failed. Line content: "${line}"`);
      }
    }
  }
  /**
   * Assert screen matches expected content or predicate
   */
  async assertScreen(expected, options) {
    if (typeof expected === "function") {
      const screenText = await this.getScreenText();
      if (!expected(screenText)) {
        throw new Error("Screen assertion failed: predicate returned false");
      }
      return;
    }
    const capture = await this.captureScreen();
    const actual = options?.ignoreAnsi ? capture.text : capture.raw;
    const expectedText = Array.isArray(expected) ? expected.join("\n") : expected;
    if (!compareScreens(actual, expectedText, options)) {
      const diff = screenDiff(actual, expectedText);
      throw new Error(`Screen assertion failed:
${diff}`);
    }
  }
  /**
   * Assert screen contains text
   */
  async assertScreenContains(text, options) {
    const capture = await this.captureScreen();
    const screenText = normalizeText(capture.text, options);
    const searchText = normalizeText(text, options);
    if (!screenText.includes(searchText)) {
      throw new Error(`Screen does not contain "${text}"`);
    }
  }
  /**
   * Assert screen matches pattern
   */
  async assertScreenMatches(pattern, options) {
    const capture = await this.captureScreen();
    const screenText = normalizeText(capture.text, options);
    if (!pattern.test(screenText)) {
      throw new Error(`Screen does not match pattern: ${pattern}`);
    }
  }
  /**
   * Assert cursor is at specific position
   */
  async assertCursorAt(position) {
    const cursor = await this.getCursor();
    if (cursor.x !== position.x || cursor.y !== position.y) {
      throw new Error(
        `Cursor position mismatch. Expected (${position.x}, ${position.y}), got (${cursor.x}, ${cursor.y})`
      );
    }
  }
  /**
   * Get current cursor position
   */
  async getCursor() {
    this.ensureRunning();
    const result = await this.adapter.exec(`tmux display -t ${this.sessionName} -p '#{cursor_x},#{cursor_y}'`);
    if (result.code !== 0) {
      throw new Error(`Failed to get cursor position: ${result.stderr}`);
    }
    const [x, y] = result.stdout.trim().split(",").map(Number);
    return { x, y };
  }
  /**
   * Take a snapshot (alias for takeSnapshot for backward compatibility)
   */
  async snapshot(name, options) {
    const { getSnapshotManager: getSnapshotManager2 } = await Promise.resolve().then(() => (init_snapshot_manager(), snapshot_manager_exports));
    const manager = getSnapshotManager2();
    const capture = await this.captureScreen();
    let content = options?.customContent || (options?.stripAnsi ? capture.text : capture.raw);
    if (options?.trim) {
      content = content.trim();
    }
    const existing = await manager.load(name);
    if (existing && !options?.updateSnapshot) {
      let matches = false;
      if (options?.compare) {
        matches = options.compare(existing, content);
      } else {
        matches = existing === content;
      }
      if (!matches) {
        const diff = screenDiff(existing, content);
        throw new Error(`Snapshot mismatch for "${name}":
${diff}`);
      }
    } else {
      await manager.save(name, content);
    }
  }
  /**
   * Take a snapshot
   */
  async takeSnapshot(name) {
    const capture = await this.captureScreen();
    const snapshotName = name || `snapshot-${Date.now()}`;
    const snapshot = {
      id: `${this.sessionName}-${snapshotName}`,
      name: snapshotName,
      capture,
      metadata: {
        sessionName: this.sessionName,
        timestamp: Date.now(),
        size: this.config.size
      }
    };
    this.snapshots.set(snapshot.id, snapshot);
    return snapshot;
  }
  /**
   * Compare current screen with snapshot
   */
  async compareSnapshot(snapshot) {
    const targetSnapshot = typeof snapshot === "string" ? this.snapshots.get(snapshot) || await this.loadSnapshot(snapshot) : snapshot;
    if (!targetSnapshot) {
      throw new Error(`Snapshot not found: ${snapshot}`);
    }
    const currentCapture = await this.captureScreen();
    return currentCapture.raw === targetSnapshot.capture.raw;
  }
  /**
   * Save snapshot to file
   */
  async saveSnapshot(snapshot, path) {
    const snapshotPath = path || `${this.config.snapshotDir}/${snapshot.id}.json`;
    const dir = snapshotPath.substring(0, snapshotPath.lastIndexOf("/"));
    await this.adapter.mkdir(dir, { recursive: true });
    await this.adapter.writeFile(snapshotPath, JSON.stringify(snapshot, null, 2));
    this.debug(`Snapshot saved to: ${snapshotPath}`);
  }
  /**
   * Load snapshot from file
   */
  async loadSnapshot(path) {
    const content = await this.adapter.readFile(path);
    const snapshot = JSON.parse(content);
    this.snapshots.set(snapshot.id, snapshot);
    return snapshot;
  }
  /**
   * Resize the terminal
   */
  async resize(size) {
    this.ensureRunning();
    const cmd = `tmux resize-window -t ${this.sessionName} -x ${size.cols} -y ${size.rows}`;
    await this.adapter.exec(cmd);
    this.config.size = size;
    this.recordEvent("resize", size);
    this.debug(`Resized to ${size.cols}x${size.rows}`);
    await this.sleep(100);
  }
  /**
   * Clear the screen
   */
  async clear() {
    this.ensureRunning();
    await this.sendKey("l", { ctrl: true });
  }
  /**
   * Reset the terminal
   */
  async reset() {
    this.ensureRunning();
    await this.adapter.exec(`tmux send-keys -t ${this.sessionName} 'reset' Enter`);
    await this.sleep(500);
  }
  /**
   * Get current terminal size
   */
  getSize() {
    return { ...this.config.size };
  }
  /**
   * Get session name
   */
  getSessionName() {
    return this.sessionName;
  }
  /**
   * Get the last captured output (for debugging)
   */
  getLastOutput() {
    return this._outputBuffer;
  }
  /**
   * Clear the output buffer
   */
  clearOutput() {
    this._outputBuffer = "";
  }
  /**
   * Execute a tmux command directly
   * Useful for advanced tmux operations not covered by higher-level methods
   */
  async exec(command) {
    const fullCommand = command.startsWith("tmux") ? command : `tmux ${command}`;
    return this.adapter.exec(fullCommand);
  }
  /**
   * Capture screen with cursor position
   * Returns both screen content and cursor position
   */
  async capture() {
    const screen = await this.captureScreen();
    const cursor = await this.getCursor();
    return {
      ...screen,
      cursor
    };
  }
  /**
   * Start recording session
   */
  startRecording() {
    if (this.recording) {
      this.debug("Recording already in progress");
      return;
    }
    this.recording = {
      startTime: Date.now(),
      events: [],
      captures: []
    };
    this.debug("Recording started");
  }
  /**
   * Stop recording and return the recording
   */
  stopRecording() {
    if (!this.recording) {
      throw new Error("No recording in progress");
    }
    const recording = this.recording;
    this.recording = null;
    this.debug(`Recording stopped. ${recording.events.length} events, ${recording.captures.length} captures`);
    return recording;
  }
  /**
   * Play back a recording
   */
  async playRecording(recording, speed = 1) {
    this.debug(`Playing recording with ${recording.events.length} events at ${speed}x speed`);
    const startTime = Date.now();
    for (const event of recording.events) {
      const eventTime = (event.timestamp - recording.startTime) / speed;
      const currentTime = Date.now() - startTime;
      if (eventTime > currentTime) {
        await this.sleep(eventTime - currentTime);
      }
      switch (event.type) {
        case "input":
          if (event.data.type === "text") {
            await this.sendText(event.data.text);
          } else if (event.data.type === "paste") {
            await this.paste(event.data.text);
          }
          break;
        case "key":
          await this.sendKey(event.data.key, event.data.modifiers);
          break;
        case "mouse":
          await this.sendMouse(event.data);
          break;
        case "resize":
          await this.resize(event.data);
          break;
      }
    }
    this.debug("Recording playback complete");
  }
  /**
   * Sleep for specified milliseconds
   */
  async sleep(ms) {
    return sleep(ms);
  }
  /**
   * Debug log
   */
  debug(message) {
    if (this.debugMode) {
      console.log(`[TmuxTester:${this.sessionName}] ${message}`);
    }
  }
  // Private helper methods
  ensureRunning() {
    if (!this.running) {
      throw new Error("Tester is not running. Call start() first.");
    }
  }
  /**
   * Send a command to the terminal (public method)
   */
  async sendCommand(command) {
    this.ensureRunning();
    const escaped = escapeShellArg(command);
    const cmd = `tmux send-keys -t ${this.sessionName} ${escaped} Enter`;
    await this.adapter.exec(cmd);
    this.recordEvent("input", { type: "command", text: command });
    await this.sleep(100);
  }
  recordEvent(type, data) {
    if (this.recording) {
      this.recording.events.push({
        timestamp: Date.now(),
        type,
        data
      });
    }
  }
};

// src/index.ts
init_adapters();

// src/helpers/test-runner.ts
init_cjs_shims();
init_utils();
var TestRunner = class {
  options;
  results = [];
  constructor(options = {}) {
    this.options = {
      beforeAll: options.beforeAll || (() => Promise.resolve()),
      afterAll: options.afterAll || (() => Promise.resolve()),
      beforeEach: options.beforeEach || (() => Promise.resolve()),
      afterEach: options.afterEach || (() => Promise.resolve()),
      timeout: options.timeout ?? 3e4,
      retries: options.retries ?? 0,
      parallel: options.parallel ?? false,
      debug: options.debug ?? false
    };
  }
  /**
   * Run a single test scenario
   */
  async runScenario(scenario2, config) {
    const startTime = Date.now();
    const stepResults = [];
    let tester = null;
    let error;
    try {
      tester = new TmuxTester({ ...config, debug: this.options.debug });
      if (scenario2.setup) {
        await scenario2.setup();
      }
      await tester.start();
      await this.options.beforeEach(tester);
      for (const step2 of scenario2.steps) {
        const stepResult = await this.runStep(step2, tester);
        stepResults.push(stepResult);
        if (!stepResult.passed) {
          error = stepResult.error;
          break;
        }
      }
    } catch (err) {
      error = err;
    } finally {
      if (tester) {
        try {
          await this.options.afterEach(tester);
          await tester.stop();
        } catch (cleanupError) {
          console.error("Cleanup error:", cleanupError);
        }
      }
      if (scenario2.teardown) {
        try {
          await scenario2.teardown();
        } catch (teardownError) {
          console.error("Teardown error:", teardownError);
        }
      }
    }
    const result = {
      scenario: scenario2.name,
      passed: !error && stepResults.every((r) => r.passed),
      duration: Date.now() - startTime,
      steps: stepResults,
      error
    };
    this.results.push(result);
    return result;
  }
  /**
   * Run a single test step
   */
  async runStep(step2, tester) {
    const startTime = Date.now();
    let error;
    let capture;
    try {
      if (step2.skipOn) {
        const runtime = this.detectRuntime();
        if (step2.skipOn.includes(runtime)) {
          return {
            name: step2.name,
            passed: true,
            duration: 0,
            capture: await tester.captureScreen()
          };
        }
      }
      const timeout = step2.timeout ?? this.options.timeout;
      await this.withTimeout(
        async () => {
          await step2.action(tester);
          capture = await tester.captureScreen();
          if (step2.assertion) {
            await step2.assertion(tester);
          }
        },
        timeout,
        `Step "${step2.name}" timed out after ${timeout}ms`
      );
    } catch (err) {
      error = err;
      try {
        capture = await tester.captureScreen();
      } catch {
      }
    }
    return {
      name: step2.name,
      passed: !error,
      duration: Date.now() - startTime,
      error,
      capture
    };
  }
  /**
   * Run multiple scenarios
   */
  async runScenarios(scenarios, config) {
    await this.options.beforeAll();
    try {
      if (this.options.parallel) {
        const promises = scenarios.map(
          (scenario2) => this.runScenarioWithRetries(scenario2, config)
        );
        await Promise.all(promises);
      } else {
        for (const scenario2 of scenarios) {
          await this.runScenarioWithRetries(scenario2, config);
        }
      }
    } finally {
      await this.options.afterAll();
    }
    return this.results;
  }
  /**
   * Run scenario with retries
   */
  async runScenarioWithRetries(scenario2, config) {
    let lastResult = null;
    for (let attempt = 0; attempt <= this.options.retries; attempt++) {
      if (attempt > 0) {
        console.log(`Retrying scenario "${scenario2.name}" (attempt ${attempt + 1}/${this.options.retries + 1})`);
        await sleep(1e3);
      }
      lastResult = await this.runScenario(scenario2, config);
      if (lastResult.passed) {
        return lastResult;
      }
    }
    return lastResult;
  }
  /**
   * Get test results
   */
  getResults() {
    return [...this.results];
  }
  /**
   * Get summary of test results
   */
  getSummary() {
    const total = this.results.length;
    const passed = this.results.filter((r) => r.passed).length;
    const failed = total - passed;
    const duration = this.results.reduce((sum, r) => sum + r.duration, 0);
    const failures = this.results.filter((r) => !r.passed).map((r) => ({
      scenario: r.scenario,
      error: r.error?.message || "Unknown error"
    }));
    return { total, passed, failed, duration, failures };
  }
  /**
   * Print test results to console
   */
  printResults() {
    const summary = this.getSummary();
    console.log("\n" + "=".repeat(60));
    console.log("Test Results");
    console.log("=".repeat(60));
    for (const result of this.results) {
      const status = result.passed ? "\u2713" : "\u2717";
      const color = result.passed ? "\x1B[32m" : "\x1B[31m";
      const reset = "\x1B[0m";
      console.log(`${color}${status}${reset} ${result.scenario} (${result.duration}ms)`);
      if (!result.passed && result.error) {
        console.log(`  Error: ${result.error.message}`);
      }
      for (const step2 of result.steps) {
        const stepStatus = step2.passed ? "  \u2713" : "  \u2717";
        const stepColor = step2.passed ? "\x1B[32m" : "\x1B[31m";
        console.log(`  ${stepColor}${stepStatus}${reset} ${step2.name} (${step2.duration}ms)`);
        if (!step2.passed && step2.error) {
          console.log(`    ${step2.error.message}`);
        }
      }
    }
    console.log("\n" + "-".repeat(60));
    console.log(`Total: ${summary.total} | Passed: ${summary.passed} | Failed: ${summary.failed}`);
    console.log(`Duration: ${summary.duration}ms`);
    if (summary.failed > 0) {
      console.log("\nFailed scenarios:");
      for (const failure of summary.failures) {
        console.log(`  - ${failure.scenario}: ${failure.error}`);
      }
    }
  }
  /**
   * Reset results
   */
  reset() {
    this.results = [];
  }
  // Private helper methods
  async withTimeout(fn, timeoutMs, message) {
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error(message)), timeoutMs);
    });
    return Promise.race([fn(), timeoutPromise]);
  }
  detectRuntime() {
    if (typeof Deno !== "undefined") return "deno";
    if (typeof Bun !== "undefined") return "bun";
    return "node";
  }
};
async function runTest(nameOrConfig, config, steps, options) {
  if (typeof nameOrConfig === "object" && "scenarios" in nameOrConfig && nameOrConfig.scenarios) {
    const runner2 = new TestRunner(options);
    const results = [];
    for (const scenario3 of nameOrConfig.scenarios) {
      const result2 = await runner2.runScenario(scenario3, nameOrConfig);
      results.push(result2);
    }
    runner2.printResults();
    const passed = results.filter((r) => r.passed).length;
    const failed = results.filter((r) => !r.passed).length;
    return {
      passed,
      failed,
      scenarios: results
    };
  }
  const name = nameOrConfig;
  const runner = new TestRunner(options);
  const scenario2 = { name, steps: steps || [] };
  const result = await runner.runScenario(scenario2, config);
  runner.printResults();
  return result;
}
function step(name, action, assertion) {
  return { name, action, assertion };
}
function scenario(name, steps, options) {
  return {
    name,
    steps,
    setup: options?.setup,
    teardown: options?.teardown
  };
}

// src/helpers/interactions.ts
init_cjs_shims();
async function navigateMenu(tester, direction, count = 1) {
  for (let i = 0; i < count; i++) {
    await tester.sendKey(direction);
    await tester.sleep(100);
  }
}
async function selectMenuItem(tester, itemText, maxAttempts = 10) {
  for (let i = 0; i < maxAttempts; i++) {
    const screen = await tester.getScreenText();
    if (screen.includes(`> ${itemText}`) || screen.includes(`\u25CF ${itemText}`)) {
      await tester.sendKey("enter");
      return;
    }
    await tester.sendKey("down");
    await tester.sleep(100);
  }
  throw new Error(`Menu item "${itemText}" not found after ${maxAttempts} attempts`);
}
async function fillField(tester, fieldName, value) {
  await tester.waitForText(fieldName);
  await tester.sendKey("a", { ctrl: true });
  await tester.sendKey("delete");
  await tester.typeText(value);
  await tester.sendKey("tab");
}
async function submitForm(tester, formData) {
  if (formData) {
    for (const [field, value] of Object.entries(formData)) {
      await tester.waitForText(field, { timeout: 5e3 });
      await tester.typeText(value);
      await tester.sendKey("enter");
      await tester.sleep(200);
    }
  } else {
    await tester.sendKey("enter");
  }
}
async function cancel(tester) {
  await tester.sendKey("escape");
}
async function confirmDialog(tester, accept = true) {
  if (accept) {
    await tester.sendKey("y");
  } else {
    await tester.sendKey("n");
  }
}
async function scroll(tester, direction, count = 1) {
  const key = direction === "up" || direction === "down" ? direction : direction.toLowerCase();
  for (let i = 0; i < count; i++) {
    await tester.sendKey(key);
    await tester.sleep(50);
  }
}
async function clickOnText(tester, text) {
  const screen = await tester.getScreenText();
  const lines = screen.split("\n");
  for (let y = 0; y < lines.length; y++) {
    const x = lines[y].indexOf(text);
    if (x !== -1) {
      await tester.sendMouse({
        type: "click",
        position: { x, y },
        button: "left"
      });
      return;
    }
  }
  throw new Error(`Text "${text}" not found on screen`);
}
async function clickAt(tester, position, button = "left") {
  await tester.sendMouse({
    type: "click",
    position,
    button
  });
}
async function drag(tester, from, to) {
  await tester.sendMouse({
    type: "down",
    position: from,
    button: "left"
  });
  await tester.sleep(100);
  await tester.sendMouse({
    type: "drag",
    position: to,
    button: "left"
  });
  await tester.sleep(100);
  await tester.sendMouse({
    type: "up",
    position: to,
    button: "left"
  });
}
async function selectText(tester, startText, endText) {
  const screen = await tester.getScreenText();
  const lines = screen.split("\n");
  let startPos = null;
  let endPos = null;
  for (let y = 0; y < lines.length; y++) {
    const startX = lines[y].indexOf(startText);
    if (startX !== -1 && !startPos) {
      startPos = { x: startX, y };
    }
    const endX = lines[y].indexOf(endText);
    if (endX !== -1) {
      endPos = { x: endX + endText.length - 1, y };
    }
  }
  if (!startPos || !endPos) {
    throw new Error("Could not find text positions for selection");
  }
  await drag(tester, startPos, endPos);
}
async function copySelection(tester) {
  await tester.sendKey("c", { ctrl: true });
}
async function pasteFromClipboard(tester) {
  await tester.sendKey("v", { ctrl: true });
}
async function executeCommand(tester, command) {
  await tester.sendText(command);
  await tester.sendKey("enter");
}
async function waitForPrompt(tester, prompt = "$", options) {
  await tester.waitForText(prompt, options);
}
async function login(tester, username, password) {
  await tester.waitForText("Username:");
  await tester.typeText(username);
  await tester.sendKey("enter");
  await tester.waitForText("Password:");
  await tester.typeText(password);
  await tester.sendKey("enter");
}
async function switchTab(tester, tabIndex) {
  await tester.sendKey(tabIndex.toString(), { alt: true });
}
async function openCommandPalette(tester) {
  await tester.sendKey("p", { ctrl: true, shift: true });
}
async function search(tester, searchText) {
  const screen = await tester.getScreenText();
  return screen.includes(searchText);
}
async function exitApplication(tester, force = false) {
  if (force) {
    await tester.sendKey("c", { ctrl: true });
  } else {
    await tester.sendKey("q");
  }
}
async function waitForLoading(tester, options) {
  const indicators = ["Loading...", "Please wait...", "\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
  await tester.waitForPattern(
    new RegExp(`^(?!.*(${indicators.join("|")})).*$`, "s"),
    options
  );
}
async function takeAnnotatedSnapshot(tester, name, annotations) {
  const snapshot = await tester.takeSnapshot(name);
  if (annotations && snapshot.metadata) {
    snapshot.metadata.annotations = annotations;
  }
  await tester.saveSnapshot(snapshot);
}

// src/index.ts
init_bun();
init_node();
init_deno();
init_snapshot_manager();
function createTester(command, options) {
  const commandArray = typeof command === "string" ? command.split(" ") : command;
  return new TmuxTester({
    command: commandArray,
    size: { cols: options?.cols ?? 80, rows: options?.rows ?? 24 },
    env: options?.env,
    cwd: options?.cwd,
    debug: options?.debug,
    shell: options?.shell,
    sessionName: options?.sessionName,
    recordingEnabled: options?.recordingEnabled,
    snapshotDir: options?.snapshotDir
  });
}

exports.TestRunner = TestRunner;
exports.TmuxTester = TmuxTester;
exports.cancel = cancel;
exports.clickAt = clickAt;
exports.clickOnText = clickOnText;
exports.compareScreens = compareScreens;
exports.confirmDialog = confirmDialog;
exports.copySelection = copySelection;
exports.createAdapter = createAdapter;
exports.createTester = createTester;
exports.default = TmuxTester;
exports.detectRuntime = detectRuntime;
exports.drag = drag;
exports.escapeRegex = escapeRegex;
exports.escapeShellArg = escapeShellArg;
exports.executeCommand = executeCommand;
exports.exitApplication = exitApplication;
exports.extractCursorPosition = extractCursorPosition;
exports.extractRegion = extractRegion;
exports.fillField = fillField;
exports.findText = findText;
exports.formatTimestamp = formatTimestamp;
exports.generateSessionName = generateSessionName;
exports.getAdapter = getAdapter;
exports.getCharWidth = getCharWidth;
exports.getSnapshotManager = getSnapshotManager;
exports.getStringWidth = getStringWidth;
exports.getTerminalSize = getTerminalSize;
exports.getTextDimensions = getTextDimensions;
exports.isCommandAvailable = isCommandAvailable;
exports.joinLines = joinLines;
exports.login = login;
exports.navigateMenu = navigateMenu;
exports.normalizeLineEndings = normalizeLineEndings;
exports.normalizeText = normalizeText;
exports.openCommandPalette = openCommandPalette;
exports.parseScreen = parseScreen;
exports.parseScreenLines = parseScreenLines;
exports.parseTmuxKey = parseTmuxKey;
exports.parseTmuxMouse = parseTmuxMouse;
exports.pasteFromClipboard = pasteFromClipboard;
exports.registerAdapter = registerAdapter;
exports.resetAdapter = resetAdapter;
exports.resetSnapshotManager = resetSnapshotManager;
exports.runTest = runTest;
exports.scenario = scenario;
exports.screenDiff = screenDiff;
exports.scroll = scroll;
exports.search = search;
exports.selectMenuItem = selectMenuItem;
exports.selectText = selectText;
exports.setAdapter = setAdapter;
exports.setDefaultAdapter = setDefaultAdapter;
exports.sleep = sleep;
exports.splitLines = splitLines;
exports.step = step;
exports.stripAnsi = stripAnsi;
exports.submitForm = submitForm;
exports.switchTab = switchTab;
exports.takeAnnotatedSnapshot = takeAnnotatedSnapshot;
exports.trimScreenContent = trimScreenContent;
exports.waitFor = waitFor;
exports.waitForCondition = waitForCondition;
exports.waitForLoading = waitForLoading;
exports.waitForPrompt = waitForPrompt;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map