name: CI on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: jobs: version-sync: name: Version Sync Check runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version-file: .node-version - name: Check version sync run: node scripts/check-version-sync.js rust: name: Rust runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - name: Cache Rust build artifacts uses: Swatinem/rust-cache@v2 with: workspaces: cli - name: Format check run: cargo fmt --manifest-path cli/Cargo.toml -- --check - name: Clippy check run: cargo clippy --manifest-path cli/Cargo.toml -- -D warnings - name: Run Rust tests run: cargo test --profile ci --manifest-path cli/Cargo.toml rust-cross: name: Rust (${{ matrix.os }} - ${{ matrix.target }}) if: github.event_name != 'pull_request' runs-on: ${{ matrix.os }} # Fail fast on a hung test instead of running to GitHub's 6h default. timeout-minutes: 30 strategy: matrix: include: - 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 Rust build artifacts uses: Swatinem/rust-cache@v2 with: workspaces: cli - name: Run Rust tests run: cargo test --profile ci --manifest-path cli/Cargo.toml --target ${{ matrix.target }} native-e2e: name: Native E2E Tests if: github.event_name != 'pull_request' runs-on: ubuntu-latest needs: rust # Fail fast on a hung e2e test instead of GitHub's 6h default. timeout-minutes: 30 # This fork forbids headless by default (always-headed for stealth), but CI # runners have no display. Opt into the documented display-less escape so # launched Chrome can start; e2e tests exercise functionality, not stealth. env: AGENT_BROWSER_ALLOW_HEADLESS: "1" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Cache Rust build artifacts uses: Swatinem/rust-cache@v2 with: workspaces: cli - name: Install Chrome run: | cargo run --manifest-path cli/Cargo.toml -- install --with-deps - name: Install ffmpeg run: sudo apt-get update && sudo apt-get install -y ffmpeg - name: Run e2e tests run: cargo test --profile ci --manifest-path cli/Cargo.toml e2e -- --ignored --test-threads=1 windows-integration: name: Windows Integration Test if: github.event_name != 'pull_request' runs-on: windows-latest needs: rust-cross # Headless-forbidden fork on a headless CI runner — opt into the escape so # `chrome-use open` can launch Chrome. env: AGENT_BROWSER_ALLOW_HEADLESS: "1" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: x86_64-pc-windows-msvc - name: Cache Rust build artifacts uses: Swatinem/rust-cache@v2 with: workspaces: cli - name: Build Rust CLI run: cargo build --release --manifest-path cli/Cargo.toml --target x86_64-pc-windows-msvc - name: Copy CLI binary to bin directory run: | Copy-Item cli/target/x86_64-pc-windows-msvc/release/chrome-use.exe bin/chrome-use-win32-x64.exe - name: Test chrome-use install command run: | $env:PATH = "$pwd\bin;$env:PATH" for ($i = 1; $i -le 3; $i++) { bin/chrome-use-win32-x64.exe install if ($LASTEXITCODE -eq 0) { exit 0 } Write-Host "Attempt $i failed, retrying in 10 seconds..." Start-Sleep -Seconds 10 } exit 1 shell: pwsh timeout-minutes: 10 - name: Test daemon lifecycle (open, snapshot, close) run: | $env:PATH = "$pwd\bin;$env:PATH" Write-Host "--- Opening page ---" # --launch: spawn a standalone browser. Without it, `open` defaults to # auto-connect and looks for an existing Chrome on a debug port — which # a fresh CI runner doesn't have, so it errors "Could not connect". bin/chrome-use-win32-x64.exe --launch open https://example.com if ($LASTEXITCODE -ne 0) { Write-Error "open failed"; exit 1 } Write-Host "--- Taking snapshot ---" $snapshot = bin/chrome-use-win32-x64.exe snapshot if ($LASTEXITCODE -ne 0) { Write-Error "snapshot failed"; exit 1 } Write-Host $snapshot Write-Host "--- Closing browser ---" bin/chrome-use-win32-x64.exe close if ($LASTEXITCODE -ne 0) { Write-Error "close failed"; exit 1 } Write-Host "--- Windows daemon lifecycle test passed ---" shell: pwsh timeout-minutes: 5 global-install: name: Global Install (${{ matrix.os }}) if: github.event_name != 'pull_request' runs-on: ${{ matrix.os }} needs: rust-cross strategy: matrix: include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu binary: chrome-use-linux-x64 - os: macos-latest target: aarch64-apple-darwin binary: chrome-use-darwin-arm64 - os: windows-latest target: x86_64-pc-windows-msvc binary: chrome-use-win32-x64.exe steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version-file: .node-version - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Cache Rust build artifacts uses: Swatinem/rust-cache@v2 with: workspaces: cli - name: Build Rust CLI run: cargo build --release --manifest-path cli/Cargo.toml --target ${{ matrix.target }} - name: Copy CLI binary to bin directory (Unix) if: runner.os != 'Windows' run: cp cli/target/${{ matrix.target }}/release/chrome-use bin/${{ matrix.binary }} - name: Copy CLI binary to bin directory (Windows) if: runner.os == 'Windows' run: Copy-Item cli/target/${{ matrix.target }}/release/chrome-use.exe bin/${{ matrix.binary }} - name: Test npm global install run: | npm pack npm install -g chrome-use-*.tgz chrome-use --version shell: bash - name: Verify symlink points to native binary (Unix) if: runner.os != 'Windows' run: | SYMLINK=$(npm prefix -g)/bin/chrome-use TARGET=$(readlink "$SYMLINK") echo "Symlink: $SYMLINK" echo "Target: $TARGET" if [[ "$TARGET" != *"${{ matrix.binary }}"* ]]; then echo "ERROR: Symlink should point to native binary, not JS wrapper" exit 1 fi echo "Symlink correctly points to native binary" shell: bash - name: Verify CLI works (and prefers the native shim) (Windows) if: runner.os == 'Windows' run: | # The CLI must work. The native-shim rewrite is a best-effort speedup # (npm often creates the .cmd AFTER postinstall runs, so the rewrite # can't happen and the JS wrapper — which spawns the native binary — is # the valid fallback). Require functionality; prefer, but don't require, # the native shim. $ver = chrome-use --version if ($LASTEXITCODE -ne 0) { Write-Error "chrome-use --version failed"; exit 1 } echo "CLI version: $ver" $content = Get-Content "$(npm prefix -g)\chrome-use.cmd" -Raw echo "Shim content:" echo $content if ($content -match "chrome-use-win32-x64\.exe") { echo "OK: shim points directly to the native binary (zero overhead)" } else { echo "INFO: shim uses the JS wrapper fallback (functional; native-shim optimization not applied)" } shell: pwsh