Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21ea2b02bd | ||
|
|
0f7d2946af | ||
|
|
5a7c7440c0 | ||
|
|
24881c7327 | ||
|
|
9f7d7d6447 |
@@ -0,0 +1,85 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
typescript:
|
||||||
|
name: TypeScript (Node ${{ matrix.node-version }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [20, 22]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: pnpm typecheck
|
||||||
|
|
||||||
|
- name: Format check
|
||||||
|
run: pnpm format:check
|
||||||
|
|
||||||
|
- name: Install Playwright browsers
|
||||||
|
run: pnpm exec playwright install --with-deps chromium
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
rust:
|
||||||
|
name: Rust (${{ matrix.os }} - ${{ matrix.target }})
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
- os: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
- os: macos-latest
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
- os: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Cache Cargo dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
cli/target/
|
||||||
|
key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('cli/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-${{ matrix.target }}-
|
||||||
|
|
||||||
|
- name: Build release binary
|
||||||
|
run: cargo build --release --manifest-path cli/Cargo.toml --target ${{ matrix.target }}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pnpm lint-staged
|
||||||
+951
-170
File diff suppressed because it is too large
Load Diff
+17
-8
@@ -8,7 +8,7 @@ use serde_json::json;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use commands::{gen_id, parse_command};
|
use commands::{gen_id, parse_command, ParseError};
|
||||||
use connection::{ensure_daemon, send_command};
|
use connection::{ensure_daemon, send_command};
|
||||||
use flags::{clean_args, parse_flags};
|
use flags::{clean_args, parse_flags};
|
||||||
use install::run_install;
|
use install::run_install;
|
||||||
@@ -32,13 +32,22 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cmd = match parse_command(&clean, &flags) {
|
let cmd = match parse_command(&clean, &flags) {
|
||||||
Some(c) => c,
|
Ok(c) => c,
|
||||||
None => {
|
Err(e) => {
|
||||||
eprintln!(
|
if flags.json {
|
||||||
"\x1b[31mUnknown command:\x1b[0m {}",
|
let error_type = match &e {
|
||||||
clean.get(0).unwrap_or(&String::new())
|
ParseError::UnknownCommand { .. } => "unknown_command",
|
||||||
);
|
ParseError::UnknownSubcommand { .. } => "unknown_subcommand",
|
||||||
eprintln!("\x1b[2mRun: agent-browser --help\x1b[0m");
|
ParseError::MissingArguments { .. } => "missing_arguments",
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
r#"{{"success":false,"error":"{}","type":"{}"}}"#,
|
||||||
|
e.format().replace('\n', " "),
|
||||||
|
error_type
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31m{}\x1b[0m", e.format());
|
||||||
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"agent-browser": "./bin/agent-browser"
|
"agent-browser": "./bin/agent-browser"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prepare": "husky",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"build:native": "cargo build --release --manifest-path cli/Cargo.toml && node scripts/copy-native.js",
|
"build:native": "cargo build --release --manifest-path cli/Cargo.toml && node scripts/copy-native.js",
|
||||||
"build:linux": "docker compose -f docker/docker-compose.yml run --rm build-linux",
|
"build:linux": "docker compose -f docker/docker-compose.yml run --rm build-linux",
|
||||||
@@ -52,10 +53,15 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.10.0",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"lint-staged": "^15.2.11",
|
||||||
"playwright": "^1.57.0",
|
"playwright": "^1.57.0",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"tsx": "^4.6.0",
|
"tsx": "^4.6.0",
|
||||||
"typescript": "^5.3.0",
|
"typescript": "^5.3.0",
|
||||||
"vitest": "^4.0.16"
|
"vitest": "^4.0.16"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"src/**/*.ts": "prettier --write"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+442
-7
@@ -18,6 +18,12 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.10.0
|
specifier: ^20.10.0
|
||||||
version: 20.19.28
|
version: 20.19.28
|
||||||
|
husky:
|
||||||
|
specifier: ^9.1.7
|
||||||
|
version: 9.1.7
|
||||||
|
lint-staged:
|
||||||
|
specifier: ^15.2.11
|
||||||
|
version: 15.5.2
|
||||||
playwright:
|
playwright:
|
||||||
specifier: ^1.57.0
|
specifier: ^1.57.0
|
||||||
version: 1.57.0
|
version: 1.57.0
|
||||||
@@ -32,7 +38,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.0.16
|
specifier: ^4.0.16
|
||||||
version: 4.0.16(@types/node@20.19.28)(tsx@4.21.0)
|
version: 4.0.16(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -364,14 +370,69 @@ packages:
|
|||||||
'@vitest/utils@4.0.16':
|
'@vitest/utils@4.0.16':
|
||||||
resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==}
|
resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==}
|
||||||
|
|
||||||
|
ansi-escapes@7.2.0:
|
||||||
|
resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
ansi-regex@6.2.2:
|
||||||
|
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
ansi-styles@6.2.3:
|
||||||
|
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
assertion-error@2.0.1:
|
assertion-error@2.0.1:
|
||||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
braces@3.0.3:
|
||||||
|
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
chai@6.2.2:
|
chai@6.2.2:
|
||||||
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
chalk@5.6.2:
|
||||||
|
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
|
||||||
|
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||||
|
|
||||||
|
cli-cursor@5.0.0:
|
||||||
|
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
cli-truncate@4.0.0:
|
||||||
|
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
colorette@2.0.20:
|
||||||
|
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||||
|
|
||||||
|
commander@13.1.0:
|
||||||
|
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
cross-spawn@7.0.6:
|
||||||
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
emoji-regex@10.6.0:
|
||||||
|
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
||||||
|
|
||||||
|
environment@1.1.0:
|
||||||
|
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
es-module-lexer@1.7.0:
|
es-module-lexer@1.7.0:
|
||||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||||
|
|
||||||
@@ -383,6 +444,13 @@ packages:
|
|||||||
estree-walker@3.0.3:
|
estree-walker@3.0.3:
|
||||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
|
||||||
|
eventemitter3@5.0.1:
|
||||||
|
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||||
|
|
||||||
|
execa@8.0.1:
|
||||||
|
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||||
|
engines: {node: '>=16.17'}
|
||||||
|
|
||||||
expect-type@1.3.0:
|
expect-type@1.3.0:
|
||||||
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@@ -396,6 +464,10 @@ packages:
|
|||||||
picomatch:
|
picomatch:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
fill-range@7.1.1:
|
||||||
|
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
fsevents@2.3.2:
|
fsevents@2.3.2:
|
||||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@@ -406,30 +478,130 @@ packages:
|
|||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
get-east-asian-width@1.4.0:
|
||||||
|
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
get-stream@8.0.1:
|
||||||
|
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
get-tsconfig@4.13.0:
|
get-tsconfig@4.13.0:
|
||||||
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
||||||
|
|
||||||
|
human-signals@5.0.0:
|
||||||
|
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||||
|
engines: {node: '>=16.17.0'}
|
||||||
|
|
||||||
|
husky@9.1.7:
|
||||||
|
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
is-fullwidth-code-point@4.0.0:
|
||||||
|
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
is-fullwidth-code-point@5.1.0:
|
||||||
|
resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
is-number@7.0.0:
|
||||||
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
|
is-stream@3.0.0:
|
||||||
|
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
|
isexe@2.0.0:
|
||||||
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
|
lilconfig@3.1.3:
|
||||||
|
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
lint-staged@15.5.2:
|
||||||
|
resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==}
|
||||||
|
engines: {node: '>=18.12.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
listr2@8.3.3:
|
||||||
|
resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
log-update@6.1.0:
|
||||||
|
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
merge-stream@2.0.0:
|
||||||
|
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||||
|
|
||||||
|
micromatch@4.0.8:
|
||||||
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
mimic-fn@4.0.0:
|
||||||
|
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
mimic-function@5.0.1:
|
||||||
|
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
nanoid@3.3.11:
|
nanoid@3.3.11:
|
||||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
npm-run-path@5.3.0:
|
||||||
|
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
obug@2.1.1:
|
obug@2.1.1:
|
||||||
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
||||||
|
|
||||||
|
onetime@6.0.0:
|
||||||
|
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
onetime@7.0.0:
|
||||||
|
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
path-key@3.1.1:
|
||||||
|
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
path-key@4.0.0:
|
||||||
|
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
pathe@2.0.3:
|
pathe@2.0.3:
|
||||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
picomatch@2.3.1:
|
||||||
|
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||||
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
picomatch@4.0.3:
|
picomatch@4.0.3:
|
||||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
pidtree@0.6.0:
|
||||||
|
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
playwright-core@1.57.0:
|
playwright-core@1.57.0:
|
||||||
resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==}
|
resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -452,14 +624,41 @@ packages:
|
|||||||
resolve-pkg-maps@1.0.0:
|
resolve-pkg-maps@1.0.0:
|
||||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||||
|
|
||||||
|
restore-cursor@5.1.0:
|
||||||
|
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
rfdc@1.4.1:
|
||||||
|
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||||
|
|
||||||
rollup@4.55.1:
|
rollup@4.55.1:
|
||||||
resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==}
|
resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==}
|
||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
shebang-command@2.0.0:
|
||||||
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
shebang-regex@3.0.0:
|
||||||
|
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
siginfo@2.0.0:
|
siginfo@2.0.0:
|
||||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
|
signal-exit@4.1.0:
|
||||||
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
slice-ansi@5.0.0:
|
||||||
|
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
slice-ansi@7.1.2:
|
||||||
|
resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -470,6 +669,22 @@ packages:
|
|||||||
std-env@3.10.0:
|
std-env@3.10.0:
|
||||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
|
string-argv@0.3.2:
|
||||||
|
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||||
|
engines: {node: '>=0.6.19'}
|
||||||
|
|
||||||
|
string-width@7.2.0:
|
||||||
|
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
strip-ansi@7.1.2:
|
||||||
|
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
strip-final-newline@3.0.0:
|
||||||
|
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
tinybench@2.9.0:
|
tinybench@2.9.0:
|
||||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
@@ -485,6 +700,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
|
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
to-regex-range@5.0.1:
|
||||||
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
|
engines: {node: '>=8.0'}
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -572,11 +791,25 @@ packages:
|
|||||||
jsdom:
|
jsdom:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
which@2.0.2:
|
||||||
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
why-is-node-running@2.3.0:
|
why-is-node-running@2.3.0:
|
||||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
wrap-ansi@9.0.2:
|
||||||
|
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
yaml@2.8.2:
|
||||||
|
resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
|
||||||
|
engines: {node: '>= 14.6'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
zod@3.25.76:
|
zod@3.25.76:
|
||||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||||
|
|
||||||
@@ -761,13 +994,13 @@ snapshots:
|
|||||||
chai: 6.2.2
|
chai: 6.2.2
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
'@vitest/mocker@4.0.16(vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0))':
|
'@vitest/mocker@4.0.16(vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 4.0.16
|
'@vitest/spy': 4.0.16
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.3.1(@types/node@20.19.28)(tsx@4.21.0)
|
vite: 7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
'@vitest/pretty-format@4.0.16':
|
'@vitest/pretty-format@4.0.16':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -791,10 +1024,51 @@ snapshots:
|
|||||||
'@vitest/pretty-format': 4.0.16
|
'@vitest/pretty-format': 4.0.16
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
|
ansi-escapes@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
environment: 1.1.0
|
||||||
|
|
||||||
|
ansi-regex@6.2.2: {}
|
||||||
|
|
||||||
|
ansi-styles@6.2.3: {}
|
||||||
|
|
||||||
assertion-error@2.0.1: {}
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
|
braces@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
fill-range: 7.1.1
|
||||||
|
|
||||||
chai@6.2.2: {}
|
chai@6.2.2: {}
|
||||||
|
|
||||||
|
chalk@5.6.2: {}
|
||||||
|
|
||||||
|
cli-cursor@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
restore-cursor: 5.1.0
|
||||||
|
|
||||||
|
cli-truncate@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
slice-ansi: 5.0.0
|
||||||
|
string-width: 7.2.0
|
||||||
|
|
||||||
|
colorette@2.0.20: {}
|
||||||
|
|
||||||
|
commander@13.1.0: {}
|
||||||
|
|
||||||
|
cross-spawn@7.0.6:
|
||||||
|
dependencies:
|
||||||
|
path-key: 3.1.1
|
||||||
|
shebang-command: 2.0.0
|
||||||
|
which: 2.0.2
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
|
emoji-regex@10.6.0: {}
|
||||||
|
|
||||||
|
environment@1.1.0: {}
|
||||||
|
|
||||||
es-module-lexer@1.7.0: {}
|
es-module-lexer@1.7.0: {}
|
||||||
|
|
||||||
esbuild@0.27.2:
|
esbuild@0.27.2:
|
||||||
@@ -830,36 +1104,141 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
|
|
||||||
|
eventemitter3@5.0.1: {}
|
||||||
|
|
||||||
|
execa@8.0.1:
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: 7.0.6
|
||||||
|
get-stream: 8.0.1
|
||||||
|
human-signals: 5.0.0
|
||||||
|
is-stream: 3.0.0
|
||||||
|
merge-stream: 2.0.0
|
||||||
|
npm-run-path: 5.3.0
|
||||||
|
onetime: 6.0.0
|
||||||
|
signal-exit: 4.1.0
|
||||||
|
strip-final-newline: 3.0.0
|
||||||
|
|
||||||
expect-type@1.3.0: {}
|
expect-type@1.3.0: {}
|
||||||
|
|
||||||
fdir@6.5.0(picomatch@4.0.3):
|
fdir@6.5.0(picomatch@4.0.3):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
|
|
||||||
|
fill-range@7.1.1:
|
||||||
|
dependencies:
|
||||||
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
fsevents@2.3.2:
|
fsevents@2.3.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
get-east-asian-width@1.4.0: {}
|
||||||
|
|
||||||
|
get-stream@8.0.1: {}
|
||||||
|
|
||||||
get-tsconfig@4.13.0:
|
get-tsconfig@4.13.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
|
human-signals@5.0.0: {}
|
||||||
|
|
||||||
|
husky@9.1.7: {}
|
||||||
|
|
||||||
|
is-fullwidth-code-point@4.0.0: {}
|
||||||
|
|
||||||
|
is-fullwidth-code-point@5.1.0:
|
||||||
|
dependencies:
|
||||||
|
get-east-asian-width: 1.4.0
|
||||||
|
|
||||||
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
|
is-stream@3.0.0: {}
|
||||||
|
|
||||||
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
|
lilconfig@3.1.3: {}
|
||||||
|
|
||||||
|
lint-staged@15.5.2:
|
||||||
|
dependencies:
|
||||||
|
chalk: 5.6.2
|
||||||
|
commander: 13.1.0
|
||||||
|
debug: 4.4.3
|
||||||
|
execa: 8.0.1
|
||||||
|
lilconfig: 3.1.3
|
||||||
|
listr2: 8.3.3
|
||||||
|
micromatch: 4.0.8
|
||||||
|
pidtree: 0.6.0
|
||||||
|
string-argv: 0.3.2
|
||||||
|
yaml: 2.8.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
listr2@8.3.3:
|
||||||
|
dependencies:
|
||||||
|
cli-truncate: 4.0.0
|
||||||
|
colorette: 2.0.20
|
||||||
|
eventemitter3: 5.0.1
|
||||||
|
log-update: 6.1.0
|
||||||
|
rfdc: 1.4.1
|
||||||
|
wrap-ansi: 9.0.2
|
||||||
|
|
||||||
|
log-update@6.1.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-escapes: 7.2.0
|
||||||
|
cli-cursor: 5.0.0
|
||||||
|
slice-ansi: 7.1.2
|
||||||
|
strip-ansi: 7.1.2
|
||||||
|
wrap-ansi: 9.0.2
|
||||||
|
|
||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
merge-stream@2.0.0: {}
|
||||||
|
|
||||||
|
micromatch@4.0.8:
|
||||||
|
dependencies:
|
||||||
|
braces: 3.0.3
|
||||||
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
mimic-fn@4.0.0: {}
|
||||||
|
|
||||||
|
mimic-function@5.0.1: {}
|
||||||
|
|
||||||
|
ms@2.1.3: {}
|
||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
|
npm-run-path@5.3.0:
|
||||||
|
dependencies:
|
||||||
|
path-key: 4.0.0
|
||||||
|
|
||||||
obug@2.1.1: {}
|
obug@2.1.1: {}
|
||||||
|
|
||||||
|
onetime@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
mimic-fn: 4.0.0
|
||||||
|
|
||||||
|
onetime@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
mimic-function: 5.0.1
|
||||||
|
|
||||||
|
path-key@3.1.1: {}
|
||||||
|
|
||||||
|
path-key@4.0.0: {}
|
||||||
|
|
||||||
pathe@2.0.3: {}
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
picomatch@2.3.1: {}
|
||||||
|
|
||||||
picomatch@4.0.3: {}
|
picomatch@4.0.3: {}
|
||||||
|
|
||||||
|
pidtree@0.6.0: {}
|
||||||
|
|
||||||
playwright-core@1.57.0: {}
|
playwright-core@1.57.0: {}
|
||||||
|
|
||||||
playwright@1.57.0:
|
playwright@1.57.0:
|
||||||
@@ -878,6 +1257,13 @@ snapshots:
|
|||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
|
|
||||||
|
restore-cursor@5.1.0:
|
||||||
|
dependencies:
|
||||||
|
onetime: 7.0.0
|
||||||
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
|
rfdc@1.4.1: {}
|
||||||
|
|
||||||
rollup@4.55.1:
|
rollup@4.55.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
@@ -909,14 +1295,46 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc': 4.55.1
|
'@rollup/rollup-win32-x64-msvc': 4.55.1
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
shebang-command@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
shebang-regex: 3.0.0
|
||||||
|
|
||||||
|
shebang-regex@3.0.0: {}
|
||||||
|
|
||||||
siginfo@2.0.0: {}
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
|
signal-exit@4.1.0: {}
|
||||||
|
|
||||||
|
slice-ansi@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 6.2.3
|
||||||
|
is-fullwidth-code-point: 4.0.0
|
||||||
|
|
||||||
|
slice-ansi@7.1.2:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 6.2.3
|
||||||
|
is-fullwidth-code-point: 5.1.0
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
stackback@0.0.2: {}
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
std-env@3.10.0: {}
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
|
string-argv@0.3.2: {}
|
||||||
|
|
||||||
|
string-width@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
emoji-regex: 10.6.0
|
||||||
|
get-east-asian-width: 1.4.0
|
||||||
|
strip-ansi: 7.1.2
|
||||||
|
|
||||||
|
strip-ansi@7.1.2:
|
||||||
|
dependencies:
|
||||||
|
ansi-regex: 6.2.2
|
||||||
|
|
||||||
|
strip-final-newline@3.0.0: {}
|
||||||
|
|
||||||
tinybench@2.9.0: {}
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
tinyexec@1.0.2: {}
|
tinyexec@1.0.2: {}
|
||||||
@@ -928,6 +1346,10 @@ snapshots:
|
|||||||
|
|
||||||
tinyrainbow@3.0.3: {}
|
tinyrainbow@3.0.3: {}
|
||||||
|
|
||||||
|
to-regex-range@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
is-number: 7.0.0
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.27.2
|
esbuild: 0.27.2
|
||||||
@@ -939,7 +1361,7 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0):
|
vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.27.2
|
esbuild: 0.27.2
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -951,11 +1373,12 @@ snapshots:
|
|||||||
'@types/node': 20.19.28
|
'@types/node': 20.19.28
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
tsx: 4.21.0
|
tsx: 4.21.0
|
||||||
|
yaml: 2.8.2
|
||||||
|
|
||||||
vitest@4.0.16(@types/node@20.19.28)(tsx@4.21.0):
|
vitest@4.0.16(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.0.16
|
'@vitest/expect': 4.0.16
|
||||||
'@vitest/mocker': 4.0.16(vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0))
|
'@vitest/mocker': 4.0.16(vite@7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
'@vitest/pretty-format': 4.0.16
|
'@vitest/pretty-format': 4.0.16
|
||||||
'@vitest/runner': 4.0.16
|
'@vitest/runner': 4.0.16
|
||||||
'@vitest/snapshot': 4.0.16
|
'@vitest/snapshot': 4.0.16
|
||||||
@@ -972,7 +1395,7 @@ snapshots:
|
|||||||
tinyexec: 1.0.2
|
tinyexec: 1.0.2
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
vite: 7.3.1(@types/node@20.19.28)(tsx@4.21.0)
|
vite: 7.3.1(@types/node@20.19.28)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 20.19.28
|
'@types/node': 20.19.28
|
||||||
@@ -989,9 +1412,21 @@ snapshots:
|
|||||||
- tsx
|
- tsx
|
||||||
- yaml
|
- yaml
|
||||||
|
|
||||||
|
which@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
isexe: 2.0.0
|
||||||
|
|
||||||
why-is-node-running@2.3.0:
|
why-is-node-running@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
siginfo: 2.0.0
|
siginfo: 2.0.0
|
||||||
stackback: 0.0.2
|
stackback: 0.0.2
|
||||||
|
|
||||||
|
wrap-ansi@9.0.2:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 6.2.3
|
||||||
|
string-width: 7.2.0
|
||||||
|
strip-ansi: 7.1.2
|
||||||
|
|
||||||
|
yaml@2.8.2: {}
|
||||||
|
|
||||||
zod@3.25.76: {}
|
zod@3.25.76: {}
|
||||||
|
|||||||
+117
-22
@@ -111,6 +111,47 @@ interface SnapshotData {
|
|||||||
refs?: Record<string, { role: string; name?: string }>;
|
refs?: Record<string, { role: string; name?: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Playwright errors to AI-friendly messages
|
||||||
|
*/
|
||||||
|
function toAIFriendlyError(error: unknown, selector: string): Error {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
// Handle strict mode violation (multiple elements match)
|
||||||
|
if (message.includes('strict mode violation')) {
|
||||||
|
// Extract count if available
|
||||||
|
const countMatch = message.match(/resolved to (\d+) elements/);
|
||||||
|
const count = countMatch ? countMatch[1] : 'multiple';
|
||||||
|
|
||||||
|
return new Error(
|
||||||
|
`Selector "${selector}" matched ${count} elements. ` +
|
||||||
|
`Run 'snapshot' to get updated refs, or use a more specific CSS selector.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle element not found
|
||||||
|
if (
|
||||||
|
message.includes('waiting for') &&
|
||||||
|
(message.includes('to be visible') || message.includes('Timeout'))
|
||||||
|
) {
|
||||||
|
return new Error(
|
||||||
|
`Element "${selector}" not found or not visible. ` +
|
||||||
|
`Run 'snapshot' to see current page elements.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle element not interactable
|
||||||
|
if (message.includes('intercepts pointer events') || message.includes('not visible')) {
|
||||||
|
return new Error(
|
||||||
|
`Element "${selector}" is not interactable (may be hidden or covered). ` +
|
||||||
|
`Try scrolling it into view or check if a modal/overlay is blocking it.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return original error for unknown cases
|
||||||
|
return error instanceof Error ? error : new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a command and return a response
|
* Execute a command and return a response
|
||||||
*/
|
*/
|
||||||
@@ -383,12 +424,16 @@ async function handleNavigate(
|
|||||||
async function handleClick(command: ClickCommand, browser: BrowserManager): Promise<Response> {
|
async function handleClick(command: ClickCommand, browser: BrowserManager): Promise<Response> {
|
||||||
// Support both refs (@e1) and regular selectors
|
// Support both refs (@e1) and regular selectors
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
|
|
||||||
await locator.click({
|
try {
|
||||||
button: command.button,
|
await locator.click({
|
||||||
clickCount: command.clickCount,
|
button: command.button,
|
||||||
delay: command.delay,
|
clickCount: command.clickCount,
|
||||||
});
|
delay: command.delay,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
|
|
||||||
return successResponse(command.id, { clicked: true });
|
return successResponse(command.id, { clicked: true });
|
||||||
}
|
}
|
||||||
@@ -396,13 +441,17 @@ async function handleClick(command: ClickCommand, browser: BrowserManager): Prom
|
|||||||
async function handleType(command: TypeCommand, browser: BrowserManager): Promise<Response> {
|
async function handleType(command: TypeCommand, browser: BrowserManager): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
|
|
||||||
if (command.clear) {
|
try {
|
||||||
await locator.fill('');
|
if (command.clear) {
|
||||||
}
|
await locator.fill('');
|
||||||
|
}
|
||||||
|
|
||||||
await locator.pressSequentially(command.text, {
|
await locator.pressSequentially(command.text, {
|
||||||
delay: command.delay,
|
delay: command.delay,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
|
|
||||||
return successResponse(command.id, { typed: true });
|
return successResponse(command.id, { typed: true });
|
||||||
}
|
}
|
||||||
@@ -449,7 +498,13 @@ async function handleScreenshot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSnapshot(
|
async function handleSnapshot(
|
||||||
command: Command & { action: 'snapshot'; interactive?: boolean; maxDepth?: number; compact?: boolean; selector?: string },
|
command: Command & {
|
||||||
|
action: 'snapshot';
|
||||||
|
interactive?: boolean;
|
||||||
|
maxDepth?: number;
|
||||||
|
compact?: boolean;
|
||||||
|
selector?: string;
|
||||||
|
},
|
||||||
browser: BrowserManager
|
browser: BrowserManager
|
||||||
): Promise<Response<SnapshotData>> {
|
): Promise<Response<SnapshotData>> {
|
||||||
// Use enhanced snapshot with refs and optional filtering
|
// Use enhanced snapshot with refs and optional filtering
|
||||||
@@ -550,14 +605,22 @@ async function handleSelect(command: SelectCommand, browser: BrowserManager): Pr
|
|||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
const values = Array.isArray(command.values) ? command.values : [command.values];
|
const values = Array.isArray(command.values) ? command.values : [command.values];
|
||||||
|
|
||||||
await locator.selectOption(values);
|
try {
|
||||||
|
await locator.selectOption(values);
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
|
|
||||||
return successResponse(command.id, { selected: values });
|
return successResponse(command.id, { selected: values });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleHover(command: HoverCommand, browser: BrowserManager): Promise<Response> {
|
async function handleHover(command: HoverCommand, browser: BrowserManager): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
await locator.hover();
|
try {
|
||||||
|
await locator.hover();
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
|
|
||||||
return successResponse(command.id, { hovered: true });
|
return successResponse(command.id, { hovered: true });
|
||||||
}
|
}
|
||||||
@@ -637,26 +700,42 @@ async function handleWindowNew(
|
|||||||
|
|
||||||
async function handleFill(command: FillCommand, browser: BrowserManager): Promise<Response> {
|
async function handleFill(command: FillCommand, browser: BrowserManager): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
await locator.fill(command.value);
|
try {
|
||||||
|
await locator.fill(command.value);
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
return successResponse(command.id, { filled: true });
|
return successResponse(command.id, { filled: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCheck(command: CheckCommand, browser: BrowserManager): Promise<Response> {
|
async function handleCheck(command: CheckCommand, browser: BrowserManager): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
await locator.check();
|
try {
|
||||||
|
await locator.check();
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
return successResponse(command.id, { checked: true });
|
return successResponse(command.id, { checked: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUncheck(command: UncheckCommand, browser: BrowserManager): Promise<Response> {
|
async function handleUncheck(command: UncheckCommand, browser: BrowserManager): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
await locator.uncheck();
|
try {
|
||||||
|
await locator.uncheck();
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
return successResponse(command.id, { unchecked: true });
|
return successResponse(command.id, { unchecked: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpload(command: UploadCommand, browser: BrowserManager): Promise<Response> {
|
async function handleUpload(command: UploadCommand, browser: BrowserManager): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
const files = Array.isArray(command.files) ? command.files : [command.files];
|
const files = Array.isArray(command.files) ? command.files : [command.files];
|
||||||
await locator.setInputFiles(files);
|
try {
|
||||||
|
await locator.setInputFiles(files);
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
return successResponse(command.id, { uploaded: files });
|
return successResponse(command.id, { uploaded: files });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,13 +744,21 @@ async function handleDoubleClick(
|
|||||||
browser: BrowserManager
|
browser: BrowserManager
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
await locator.dblclick();
|
try {
|
||||||
|
await locator.dblclick();
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
return successResponse(command.id, { clicked: true });
|
return successResponse(command.id, { clicked: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFocus(command: FocusCommand, browser: BrowserManager): Promise<Response> {
|
async function handleFocus(command: FocusCommand, browser: BrowserManager): Promise<Response> {
|
||||||
const locator = browser.getLocator(command.selector);
|
const locator = browser.getLocator(command.selector);
|
||||||
await locator.focus();
|
try {
|
||||||
|
await locator.focus();
|
||||||
|
} catch (error) {
|
||||||
|
throw toAIFriendlyError(error, command.selector);
|
||||||
|
}
|
||||||
return successResponse(command.id, { focused: true });
|
return successResponse(command.id, { focused: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,7 +878,15 @@ async function handleCookiesSet(
|
|||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const page = browser.getPage();
|
const page = browser.getPage();
|
||||||
const context = page.context();
|
const context = page.context();
|
||||||
await context.addCookies(command.cookies);
|
// Auto-fill URL for cookies that don't have domain/path/url set
|
||||||
|
const pageUrl = page.url();
|
||||||
|
const cookies = command.cookies.map((cookie) => {
|
||||||
|
if (!cookie.url && !cookie.domain && !cookie.path) {
|
||||||
|
return { ...cookie, url: pageUrl };
|
||||||
|
}
|
||||||
|
return cookie;
|
||||||
|
});
|
||||||
|
await context.addCookies(cookies);
|
||||||
return successResponse(command.id, { set: true });
|
return successResponse(command.id, { set: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+89
-2
@@ -120,6 +120,30 @@ describe('BrowserManager', () => {
|
|||||||
expect(testCookie?.value).toBe('value');
|
expect(testCookie?.value).toBe('value');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set cookie with domain', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const context = page.context();
|
||||||
|
await context.addCookies([
|
||||||
|
{ name: 'domainCookie', value: 'domainValue', domain: 'example.com', path: '/' },
|
||||||
|
]);
|
||||||
|
const cookies = await context.cookies();
|
||||||
|
const testCookie = cookies.find((c) => c.name === 'domainCookie');
|
||||||
|
expect(testCookie?.value).toBe('domainValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set multiple cookies at once', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const context = page.context();
|
||||||
|
await context.clearCookies();
|
||||||
|
await context.addCookies([
|
||||||
|
{ name: 'cookie1', value: 'value1', url: 'https://example.com' },
|
||||||
|
{ name: 'cookie2', value: 'value2', url: 'https://example.com' },
|
||||||
|
]);
|
||||||
|
const cookies = await context.cookies();
|
||||||
|
expect(cookies.find((c) => c.name === 'cookie1')?.value).toBe('value1');
|
||||||
|
expect(cookies.find((c) => c.name === 'cookie2')?.value).toBe('value2');
|
||||||
|
});
|
||||||
|
|
||||||
it('should clear cookies', async () => {
|
it('should clear cookies', async () => {
|
||||||
const page = browser.getPage();
|
const page = browser.getPage();
|
||||||
const context = page.context();
|
const context = page.context();
|
||||||
@@ -129,20 +153,83 @@ describe('BrowserManager', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('storage via evaluate', () => {
|
describe('localStorage operations', () => {
|
||||||
it('should set and get localStorage', async () => {
|
it('should set and get localStorage item', async () => {
|
||||||
const page = browser.getPage();
|
const page = browser.getPage();
|
||||||
|
await page.goto('https://example.com');
|
||||||
await page.evaluate(() => localStorage.setItem('testKey', 'testValue'));
|
await page.evaluate(() => localStorage.setItem('testKey', 'testValue'));
|
||||||
const value = await page.evaluate(() => localStorage.getItem('testKey'));
|
const value = await page.evaluate(() => localStorage.getItem('testKey'));
|
||||||
expect(value).toBe('testValue');
|
expect(value).toBe('testValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get all localStorage items', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.evaluate(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
localStorage.setItem('key1', 'value1');
|
||||||
|
localStorage.setItem('key2', 'value2');
|
||||||
|
});
|
||||||
|
const storage = await page.evaluate(() => {
|
||||||
|
const items: Record<string, string> = {};
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const key = localStorage.key(i);
|
||||||
|
if (key) items[key] = localStorage.getItem(key) || '';
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
expect(storage.key1).toBe('value1');
|
||||||
|
expect(storage.key2).toBe('value2');
|
||||||
|
});
|
||||||
|
|
||||||
it('should clear localStorage', async () => {
|
it('should clear localStorage', async () => {
|
||||||
const page = browser.getPage();
|
const page = browser.getPage();
|
||||||
await page.evaluate(() => localStorage.clear());
|
await page.evaluate(() => localStorage.clear());
|
||||||
const value = await page.evaluate(() => localStorage.getItem('testKey'));
|
const value = await page.evaluate(() => localStorage.getItem('testKey'));
|
||||||
expect(value).toBeNull();
|
expect(value).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return null for non-existent key', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.evaluate(() => localStorage.clear());
|
||||||
|
const value = await page.evaluate(() => localStorage.getItem('nonexistent'));
|
||||||
|
expect(value).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sessionStorage operations', () => {
|
||||||
|
it('should set and get sessionStorage item', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.goto('https://example.com');
|
||||||
|
await page.evaluate(() => sessionStorage.setItem('sessionKey', 'sessionValue'));
|
||||||
|
const value = await page.evaluate(() => sessionStorage.getItem('sessionKey'));
|
||||||
|
expect(value).toBe('sessionValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get all sessionStorage items', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.evaluate(() => {
|
||||||
|
sessionStorage.clear();
|
||||||
|
sessionStorage.setItem('skey1', 'svalue1');
|
||||||
|
sessionStorage.setItem('skey2', 'svalue2');
|
||||||
|
});
|
||||||
|
const storage = await page.evaluate(() => {
|
||||||
|
const items: Record<string, string> = {};
|
||||||
|
for (let i = 0; i < sessionStorage.length; i++) {
|
||||||
|
const key = sessionStorage.key(i);
|
||||||
|
if (key) items[key] = sessionStorage.getItem(key) || '';
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
expect(storage.skey1).toBe('svalue1');
|
||||||
|
expect(storage.skey2).toBe('svalue2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear sessionStorage', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.evaluate(() => sessionStorage.clear());
|
||||||
|
const value = await page.evaluate(() => sessionStorage.getItem('sessionKey'));
|
||||||
|
expect(value).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('viewport', () => {
|
describe('viewport', () => {
|
||||||
|
|||||||
+12
-4
@@ -94,13 +94,21 @@ export class BrowserManager {
|
|||||||
if (!refData) return null;
|
if (!refData) return null;
|
||||||
|
|
||||||
const page = this.getPage();
|
const page = this.getPage();
|
||||||
|
|
||||||
// Parse the selector and create locator
|
// Build locator with exact: true to avoid substring matches
|
||||||
|
let locator: Locator;
|
||||||
if (refData.name) {
|
if (refData.name) {
|
||||||
return page.getByRole(refData.role as any, { name: refData.name });
|
locator = page.getByRole(refData.role as any, { name: refData.name, exact: true });
|
||||||
} else {
|
} else {
|
||||||
return page.getByRole(refData.role as any);
|
locator = page.getByRole(refData.role as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If an nth index is stored (for disambiguation), use it
|
||||||
|
if (refData.nth !== undefined) {
|
||||||
|
locator = locator.nth(refData.nth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return locator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-2
@@ -33,7 +33,7 @@ export function getSession(): string {
|
|||||||
function getPortForSession(session: string): number {
|
function getPortForSession(session: string): number {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
for (let i = 0; i < session.length; i++) {
|
for (let i = 0; i < session.length; i++) {
|
||||||
hash = ((hash << 5) - hash) + session.charCodeAt(i);
|
hash = (hash << 5) - hash + session.charCodeAt(i);
|
||||||
hash |= 0;
|
hash |= 0;
|
||||||
}
|
}
|
||||||
// Port range 49152-65535 (dynamic/private ports)
|
// Port range 49152-65535 (dynamic/private ports)
|
||||||
@@ -90,7 +90,9 @@ export function isDaemonRunning(session?: string): boolean {
|
|||||||
* Get connection info for the current session
|
* Get connection info for the current session
|
||||||
* Returns { type: 'unix', path: string } or { type: 'tcp', port: number }
|
* Returns { type: 'unix', path: string } or { type: 'tcp', port: number }
|
||||||
*/
|
*/
|
||||||
export function getConnectionInfo(session?: string): { type: 'unix'; path: string } | { type: 'tcp'; port: number } {
|
export function getConnectionInfo(
|
||||||
|
session?: string
|
||||||
|
): { type: 'unix'; path: string } | { type: 'tcp'; port: number } {
|
||||||
const sess = session ?? currentSession;
|
const sess = session ?? currentSession;
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
return { type: 'tcp', port: getPortForSession(sess) };
|
return { type: 'tcp', port: getPortForSession(sess) };
|
||||||
|
|||||||
+198
-13
@@ -112,9 +112,22 @@ describe('parseCommand', () => {
|
|||||||
it('should parse cookies_get', () => {
|
it('should parse cookies_get', () => {
|
||||||
const result = parseCommand(cmd({ id: '1', action: 'cookies_get' }));
|
const result = parseCommand(cmd({ id: '1', action: 'cookies_get' }));
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('cookies_get');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse cookies_set', () => {
|
it('should parse cookies_get with urls filter', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'cookies_get', urls: ['https://example.com'] })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.urls).toEqual(['https://example.com']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse cookies_set with minimal cookie', () => {
|
||||||
const result = parseCommand(
|
const result = parseCommand(
|
||||||
cmd({
|
cmd({
|
||||||
id: '1',
|
id: '1',
|
||||||
@@ -123,18 +136,129 @@ describe('parseCommand', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('cookies_set');
|
||||||
|
expect(result.command.cookies).toHaveLength(1);
|
||||||
|
expect(result.command.cookies[0].name).toBe('session');
|
||||||
|
expect(result.command.cookies[0].value).toBe('abc123');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse cookies_set with full cookie options', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'cookies_set',
|
||||||
|
cookies: [
|
||||||
|
{
|
||||||
|
name: 'auth',
|
||||||
|
value: 'token123',
|
||||||
|
domain: 'example.com',
|
||||||
|
path: '/',
|
||||||
|
expires: Date.now() / 1000 + 3600,
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'Strict',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.cookies[0].httpOnly).toBe(true);
|
||||||
|
expect(result.command.cookies[0].secure).toBe(true);
|
||||||
|
expect(result.command.cookies[0].sameSite).toBe('Strict');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse cookies_set with multiple cookies', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'cookies_set',
|
||||||
|
cookies: [
|
||||||
|
{ name: 'cookie1', value: 'value1' },
|
||||||
|
{ name: 'cookie2', value: 'value2' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.cookies).toHaveLength(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject cookies_set without cookies array', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'cookies_set' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept cookies_set with empty cookies array', () => {
|
||||||
|
// Empty array is technically valid (no-op)
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'cookies_set', cookies: [] }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject cookies_set with cookie missing name', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'cookies_set', cookies: [{ value: 'test' }] })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject cookies_set with cookie missing value', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'cookies_set', cookies: [{ name: 'test' }] })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject cookies_set with invalid sameSite value', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'cookies_set',
|
||||||
|
cookies: [{ name: 'test', value: 'val', sameSite: 'Invalid' }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse cookies_clear', () => {
|
it('should parse cookies_clear', () => {
|
||||||
const result = parseCommand(cmd({ id: '1', action: 'cookies_clear' }));
|
const result = parseCommand(cmd({ id: '1', action: 'cookies_clear' }));
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('cookies_clear');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('storage', () => {
|
describe('storage', () => {
|
||||||
it('should parse storage_get', () => {
|
it('should parse storage_get for localStorage', () => {
|
||||||
const result = parseCommand(cmd({ id: '1', action: 'storage_get', type: 'local' }));
|
const result = parseCommand(cmd({ id: '1', action: 'storage_get', type: 'local' }));
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('storage_get');
|
||||||
|
expect(result.command.type).toBe('local');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse storage_get for sessionStorage', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'storage_get', type: 'session' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.type).toBe('session');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse storage_get with specific key', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'storage_get', type: 'local', key: 'mykey' })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.key).toBe('mykey');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse storage_set', () => {
|
it('should parse storage_set', () => {
|
||||||
@@ -148,6 +272,59 @@ describe('parseCommand', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('storage_set');
|
||||||
|
expect(result.command.key).toBe('test');
|
||||||
|
expect(result.command.value).toBe('value');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject storage_set without key', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'storage_set',
|
||||||
|
type: 'local',
|
||||||
|
value: 'value',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject storage_set without value', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'storage_set',
|
||||||
|
type: 'local',
|
||||||
|
key: 'test',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse storage_clear for localStorage', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'storage_clear', type: 'local' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('storage_clear');
|
||||||
|
expect(result.command.type).toBe('local');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse storage_clear for sessionStorage', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'storage_clear', type: 'session' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject storage_get without type', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'storage_get' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject storage_get with invalid type', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'storage_get', type: 'invalid' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -251,14 +428,16 @@ describe('parseCommand', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should parse snapshot with all options', () => {
|
it('should parse snapshot with all options', () => {
|
||||||
const result = parseCommand(cmd({
|
const result = parseCommand(
|
||||||
id: '1',
|
cmd({
|
||||||
action: 'snapshot',
|
id: '1',
|
||||||
interactive: true,
|
action: 'snapshot',
|
||||||
compact: true,
|
interactive: true,
|
||||||
maxDepth: 5,
|
compact: true,
|
||||||
selector: '.content',
|
maxDepth: 5,
|
||||||
}));
|
selector: '.content',
|
||||||
|
})
|
||||||
|
);
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
expect(result.command.interactive).toBe(true);
|
expect(result.command.interactive).toBe(true);
|
||||||
@@ -312,7 +491,9 @@ describe('parseCommand', () => {
|
|||||||
|
|
||||||
describe('scroll', () => {
|
describe('scroll', () => {
|
||||||
it('should parse scroll command', () => {
|
it('should parse scroll command', () => {
|
||||||
const result = parseCommand(cmd({ id: '1', action: 'scroll', direction: 'down', amount: 300 }));
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'scroll', direction: 'down', amount: 300 })
|
||||||
|
);
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -346,7 +527,9 @@ describe('parseCommand', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should parse geolocation', () => {
|
it('should parse geolocation', () => {
|
||||||
const result = parseCommand(cmd({ id: '1', action: 'geolocation', latitude: 37.7749, longitude: -122.4194 }));
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'geolocation', latitude: 37.7749, longitude: -122.4194 })
|
||||||
|
);
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -397,7 +580,9 @@ describe('parseCommand', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should parse dialog accept with prompt text', () => {
|
it('should parse dialog accept with prompt text', () => {
|
||||||
const result = parseCommand(cmd({ id: '1', action: 'dialog', response: 'accept', promptText: 'hello' }));
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'dialog', response: 'accept', promptText: 'hello' })
|
||||||
|
);
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
expect(result.command.promptText).toBe('hello');
|
expect(result.command.promptText).toBe('hello');
|
||||||
|
|||||||
+104
-19
@@ -24,6 +24,8 @@ export interface RefMap {
|
|||||||
selector: string;
|
selector: string;
|
||||||
role: string;
|
role: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
/** Index for disambiguation when multiple elements have same role+name */
|
||||||
|
nth?: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +131,7 @@ const STRUCTURAL_ROLES = new Set([
|
|||||||
function buildSelector(role: string, name?: string): string {
|
function buildSelector(role: string, name?: string): string {
|
||||||
if (name) {
|
if (name) {
|
||||||
const escapedName = name.replace(/"/g, '\\"');
|
const escapedName = name.replace(/"/g, '\\"');
|
||||||
return `getByRole('${role}', { name: "${escapedName}" })`;
|
return `getByRole('${role}', { name: "${escapedName}", exact: true })`;
|
||||||
}
|
}
|
||||||
return `getByRole('${role}')`;
|
return `getByRole('${role}')`;
|
||||||
}
|
}
|
||||||
@@ -161,49 +163,109 @@ export async function getEnhancedSnapshot(
|
|||||||
return { tree: enhancedTree, refs };
|
return { tree: enhancedTree, refs };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track role+name combinations to detect duplicates
|
||||||
|
*/
|
||||||
|
interface RoleNameTracker {
|
||||||
|
counts: Map<string, number>;
|
||||||
|
/** Maps role+name key to array of ref IDs that use it */
|
||||||
|
refsByKey: Map<string, string[]>;
|
||||||
|
getKey(role: string, name?: string): string;
|
||||||
|
getNextIndex(role: string, name?: string): number;
|
||||||
|
trackRef(role: string, name: string | undefined, ref: string): void;
|
||||||
|
/** Get all role+name keys that have duplicates */
|
||||||
|
getDuplicateKeys(): Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRoleNameTracker(): RoleNameTracker {
|
||||||
|
const counts = new Map<string, number>();
|
||||||
|
const refsByKey = new Map<string, string[]>();
|
||||||
|
return {
|
||||||
|
counts,
|
||||||
|
refsByKey,
|
||||||
|
getKey(role: string, name?: string): string {
|
||||||
|
return `${role}:${name ?? ''}`;
|
||||||
|
},
|
||||||
|
getNextIndex(role: string, name?: string): number {
|
||||||
|
const key = this.getKey(role, name);
|
||||||
|
const current = counts.get(key) ?? 0;
|
||||||
|
counts.set(key, current + 1);
|
||||||
|
return current;
|
||||||
|
},
|
||||||
|
trackRef(role: string, name: string | undefined, ref: string): void {
|
||||||
|
const key = this.getKey(role, name);
|
||||||
|
const refs = refsByKey.get(key) ?? [];
|
||||||
|
refs.push(ref);
|
||||||
|
refsByKey.set(key, refs);
|
||||||
|
},
|
||||||
|
getDuplicateKeys(): Set<string> {
|
||||||
|
const duplicates = new Set<string>();
|
||||||
|
for (const [key, refs] of refsByKey) {
|
||||||
|
if (refs.length > 1) {
|
||||||
|
duplicates.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return duplicates;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process ARIA snapshot: add refs and apply filters
|
* Process ARIA snapshot: add refs and apply filters
|
||||||
*/
|
*/
|
||||||
function processAriaTree(ariaTree: string, refs: RefMap, options: SnapshotOptions): string {
|
function processAriaTree(ariaTree: string, refs: RefMap, options: SnapshotOptions): string {
|
||||||
const lines = ariaTree.split('\n');
|
const lines = ariaTree.split('\n');
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
|
const tracker = createRoleNameTracker();
|
||||||
|
|
||||||
// For interactive-only mode, we collect just interactive elements
|
// For interactive-only mode, we collect just interactive elements
|
||||||
if (options.interactive) {
|
if (options.interactive) {
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);
|
const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);
|
||||||
if (!match) continue;
|
if (!match) continue;
|
||||||
|
|
||||||
const [, , role, name, suffix] = match;
|
const [, , role, name, suffix] = match;
|
||||||
const roleLower = role.toLowerCase();
|
const roleLower = role.toLowerCase();
|
||||||
|
|
||||||
if (INTERACTIVE_ROLES.has(roleLower)) {
|
if (INTERACTIVE_ROLES.has(roleLower)) {
|
||||||
const ref = nextRef();
|
const ref = nextRef();
|
||||||
|
const nth = tracker.getNextIndex(roleLower, name);
|
||||||
|
tracker.trackRef(roleLower, name, ref);
|
||||||
refs[ref] = {
|
refs[ref] = {
|
||||||
selector: buildSelector(roleLower, name),
|
selector: buildSelector(roleLower, name),
|
||||||
role: roleLower,
|
role: roleLower,
|
||||||
name,
|
name,
|
||||||
|
nth, // Always store nth, we'll use it for duplicates
|
||||||
};
|
};
|
||||||
|
|
||||||
let enhanced = `- ${role}`;
|
let enhanced = `- ${role}`;
|
||||||
if (name) enhanced += ` "${name}"`;
|
if (name) enhanced += ` "${name}"`;
|
||||||
enhanced += ` [ref=${ref}]`;
|
enhanced += ` [ref=${ref}]`;
|
||||||
|
// Only show nth in output if it's > 0 (for readability)
|
||||||
|
if (nth > 0) enhanced += ` [nth=${nth}]`;
|
||||||
if (suffix && suffix.includes('[')) enhanced += suffix;
|
if (suffix && suffix.includes('[')) enhanced += suffix;
|
||||||
|
|
||||||
result.push(enhanced);
|
result.push(enhanced);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Post-process: remove nth from refs that don't have duplicates
|
||||||
|
removeNthFromNonDuplicates(refs, tracker);
|
||||||
|
|
||||||
return result.join('\n') || '(no interactive elements)';
|
return result.join('\n') || '(no interactive elements)';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal processing with depth/compact filters
|
// Normal processing with depth/compact filters
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const processed = processLine(line, refs, options);
|
const processed = processLine(line, refs, options, tracker);
|
||||||
if (processed !== null) {
|
if (processed !== null) {
|
||||||
result.push(processed);
|
result.push(processed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Post-process: remove nth from refs that don't have duplicates
|
||||||
|
removeNthFromNonDuplicates(refs, tracker);
|
||||||
|
|
||||||
// If compact mode, remove empty structural elements
|
// If compact mode, remove empty structural elements
|
||||||
if (options.compact) {
|
if (options.compact) {
|
||||||
return compactTree(result.join('\n'));
|
return compactTree(result.join('\n'));
|
||||||
@@ -212,6 +274,22 @@ function processAriaTree(ariaTree: string, refs: RefMap, options: SnapshotOption
|
|||||||
return result.join('\n');
|
return result.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove nth from refs that ended up not having duplicates
|
||||||
|
* This keeps single-element locators simple (no unnecessary .nth(0))
|
||||||
|
*/
|
||||||
|
function removeNthFromNonDuplicates(refs: RefMap, tracker: RoleNameTracker): void {
|
||||||
|
const duplicateKeys = tracker.getDuplicateKeys();
|
||||||
|
|
||||||
|
for (const [ref, data] of Object.entries(refs)) {
|
||||||
|
const key = tracker.getKey(data.role, data.name);
|
||||||
|
if (!duplicateKeys.has(key)) {
|
||||||
|
// Not a duplicate, remove nth to keep locator simple
|
||||||
|
delete refs[ref].nth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get indentation level (number of spaces / 2)
|
* Get indentation level (number of spaces / 2)
|
||||||
*/
|
*/
|
||||||
@@ -226,7 +304,8 @@ function getIndentLevel(line: string): number {
|
|||||||
function processLine(
|
function processLine(
|
||||||
line: string,
|
line: string,
|
||||||
refs: RefMap,
|
refs: RefMap,
|
||||||
options: SnapshotOptions
|
options: SnapshotOptions,
|
||||||
|
tracker: RoleNameTracker
|
||||||
): string | null {
|
): string | null {
|
||||||
const depth = getIndentLevel(line);
|
const depth = getIndentLevel(line);
|
||||||
|
|
||||||
@@ -277,17 +356,22 @@ function processLine(
|
|||||||
|
|
||||||
if (shouldHaveRef) {
|
if (shouldHaveRef) {
|
||||||
const ref = nextRef();
|
const ref = nextRef();
|
||||||
|
const nth = tracker.getNextIndex(roleLower, name);
|
||||||
|
tracker.trackRef(roleLower, name, ref);
|
||||||
|
|
||||||
refs[ref] = {
|
refs[ref] = {
|
||||||
selector: buildSelector(roleLower, name),
|
selector: buildSelector(roleLower, name),
|
||||||
role: roleLower,
|
role: roleLower,
|
||||||
name,
|
name,
|
||||||
|
nth, // Always store nth, we'll clean up non-duplicates later
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build enhanced line with ref
|
// Build enhanced line with ref
|
||||||
let enhanced = `${prefix}${role}`;
|
let enhanced = `${prefix}${role}`;
|
||||||
if (name) enhanced += ` "${name}"`;
|
if (name) enhanced += ` "${name}"`;
|
||||||
enhanced += ` [ref=${ref}]`;
|
enhanced += ` [ref=${ref}]`;
|
||||||
|
// Only show nth in output if it's > 0 (for readability)
|
||||||
|
if (nth > 0) enhanced += ` [nth=${nth}]`;
|
||||||
if (suffix) enhanced += suffix;
|
if (suffix) enhanced += suffix;
|
||||||
|
|
||||||
return enhanced;
|
return enhanced;
|
||||||
@@ -302,27 +386,27 @@ function processLine(
|
|||||||
function compactTree(tree: string): string {
|
function compactTree(tree: string): string {
|
||||||
const lines = tree.split('\n');
|
const lines = tree.split('\n');
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
|
|
||||||
// Simple pass: keep lines that have content or refs
|
// Simple pass: keep lines that have content or refs
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
|
|
||||||
// Always keep lines with refs
|
// Always keep lines with refs
|
||||||
if (line.includes('[ref=')) {
|
if (line.includes('[ref=')) {
|
||||||
result.push(line);
|
result.push(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep lines with text content (after :)
|
// Keep lines with text content (after :)
|
||||||
if (line.includes(':') && !line.endsWith(':')) {
|
if (line.includes(':') && !line.endsWith(':')) {
|
||||||
result.push(line);
|
result.push(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this structural element has children with refs
|
// Check if this structural element has children with refs
|
||||||
const currentIndent = getIndentLevel(line);
|
const currentIndent = getIndentLevel(line);
|
||||||
let hasRelevantChildren = false;
|
let hasRelevantChildren = false;
|
||||||
|
|
||||||
for (let j = i + 1; j < lines.length; j++) {
|
for (let j = i + 1; j < lines.length; j++) {
|
||||||
const childIndent = getIndentLevel(lines[j]);
|
const childIndent = getIndentLevel(lines[j]);
|
||||||
if (childIndent <= currentIndent) break;
|
if (childIndent <= currentIndent) break;
|
||||||
@@ -331,12 +415,12 @@ function compactTree(tree: string): string {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasRelevantChildren) {
|
if (hasRelevantChildren) {
|
||||||
result.push(line);
|
result.push(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.join('\n');
|
return result.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,17 +443,18 @@ export function parseRef(arg: string): string | null {
|
|||||||
/**
|
/**
|
||||||
* Get snapshot statistics
|
* Get snapshot statistics
|
||||||
*/
|
*/
|
||||||
export function getSnapshotStats(tree: string, refs: RefMap): {
|
export function getSnapshotStats(
|
||||||
|
tree: string,
|
||||||
|
refs: RefMap
|
||||||
|
): {
|
||||||
lines: number;
|
lines: number;
|
||||||
chars: number;
|
chars: number;
|
||||||
tokens: number;
|
tokens: number;
|
||||||
refs: number;
|
refs: number;
|
||||||
interactive: number;
|
interactive: number;
|
||||||
} {
|
} {
|
||||||
const interactive = Object.values(refs).filter(r =>
|
const interactive = Object.values(refs).filter((r) => INTERACTIVE_ROLES.has(r.role)).length;
|
||||||
INTERACTIVE_ROLES.has(r.role)
|
|
||||||
).length;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lines: tree.split('\n').length,
|
lines: tree.split('\n').length,
|
||||||
chars: tree.length,
|
chars: tree.length,
|
||||||
|
|||||||
Reference in New Issue
Block a user