Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/e2e-repl-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: E2E REPL Test 🧪

on:
workflow_dispatch: ~
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
e2e_repl_test:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- name: ⚡ Setup Golang
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true

- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: '1.91.1'
default: true
profile: minimal
components: rustfmt, clippy

- name: ⚡ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: tavern/internal/www/package-lock.json

- name: 📦 Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler libssl-dev
- name: 🔨 Build Tavern
run: |
go mod download
go build -v -o tavern_bin ./tavern
- name: 🚀 Run Tavern
env:
HTTP_LISTEN_ADDR: ":8000"
run: |
./tavern_bin &
echo "Waiting for Tavern to start..."
# Wait for port 8000
timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' localhost 8000
- name: 🤖 Run Agent
working-directory: implants/imixv2
env:
IMIX_CALLBACK_URI: "http://localhost:8000"
IMIX_CALLBACK_INTERVAL: 1
run: |
# Fetch the pubkey and verify it's not empty
PUBKEY=$(curl -s http://localhost:8000/status | jq -r .Pubkey)
if [ -z "$PUBKEY" ] || [ "$PUBKEY" == "null" ]; then
echo "Error: Could not fetch Pubkey from Tavern"
exit 1
fi
export IMIX_SERVER_PUBKEY=$PUBKEY
echo "Got pubkey: $IMIX_SERVER_PUBKEY"

echo "Building imixv2..."
cargo build --bin imixv2 --target-dir ./build
# Run agent and pipe logs to a file
./build/debug/imixv2 > agent.log 2>&1 &

# Give the agent a moment to perform the initial handshake
echo "Agent started. Waiting for initial callback..."
sleep 5
- name: 🎭 Install Playwright
working-directory: tests/e2e
run: |
npm ci
npx playwright install --with-deps chromium
- name: 🧪 Run E2E Tests
working-directory: tests/e2e
run: |
npx playwright test
- name: 📂 Upload Service Logs
if: always() # Runs even if tests fail
uses: actions/upload-artifact@v4
with:
name: e2e-logs
path: |
tavern.log
implants/imixv2/agent.log
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
# Build artifacts
dist/**

# Playwright E2E Test Results
tests/e2e/test-results/**
tests/e2e/playwright-report/**

# Binaries for programs and plugins
*.exe
*.exe~
Expand Down
96 changes: 96 additions & 0 deletions tests/e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 13 additions & 6 deletions tests/e2e/tests/e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import { test, expect } from '@playwright/test';

test('End-to-end reverse shell repl test', async ({ page }) => {
// 1. Connect to tavern's UI using playwright at http://127.0.0.1:8000/createQuest
// Connect to tavern's UI using playwright at http://127.0.0.1:8000/createQuest
console.log('Navigating to /createQuest');
await page.goto('/createQuest');

// 2. Select the only visible beacon and click "continue"
// Select the only visible beacon and click "continue"
console.log('Waiting for beacons to load');
await expect(page.getByText('Loading beacons...')).toBeHidden({ timeout: 15000 });

// Select the checkbox. Using force: true because Chakra UI hides the actual input.
// Define the locator for the beacon checkboxes
const beacons = page.locator('.chakra-card input[type="checkbox"]');

// Assert that exactly one beacon exists
await expect(beacons).toHaveCount(1);

// Select the beacon
console.log('Selecting beacon');
await page.locator('input[type="checkbox"]').first().check({ force: true });
await beacons.first().check({ force: true });

// Click Continue
console.log('Clicking Continue (Beacon)');
await page.getByRole('button', { name: 'Continue' }).click();

// 3. Select the "Reverse Shell Repl" tome and click "continue"
// 3. Select the "Reverse Shell REPL" tome and click "continue"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number still here

console.log('Selecting Tome');
await expect(page.getByText('Loading tomes...')).toBeHidden();
await page.getByText('Reverse Shell Repl').click();
await page.getByText('Reverse Shell REPL').click();

console.log('Clicking Continue (Tome)');
await page.getByRole('button', { name: 'Continue' }).click();
Expand Down Expand Up @@ -52,7 +59,7 @@ test('End-to-end reverse shell repl test', async ({ page }) => {
await expect(page.locator('#terminal')).toBeVisible({ timeout: 15000 });

// Focus the terminal (clicking it helps ensure focus)
await page.locator('.xterm-cursor-layer').click();
await page.locator('#terminal').click();

console.log('Sending command');
// Type something.
Expand Down
Loading