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
1 change: 1 addition & 0 deletions docs/content/docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ npx toolproof

- [Syntax and Terminology](syntax/): Learn the full syntax for writing tests
- [Browser Testing](browser-testing/): Comprehensive guide to testing web applications
- [Debugger Mode](debugger/): Step through tests interactively for debugging and development
- [Using Macros](macros/): Create reusable step sequences
- [Snapshot Testing](snapshots/): Snapshot test long or complex output
- [Configuration](configuration/): Configure Toolproof for your project
Expand Down
3 changes: 3 additions & 0 deletions docs/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ All configuration options that can be set via command-line or environment variab
| `supported_versions` | String | Error if Toolproof version doesn't match this range |
| `failure_screenshot_location` | String | Directory to save browser screenshots when tests fail |
| `retry_count` | Number | Number of times to retry failed tests before marking as failed |
| `debugger` | Boolean | Run in debugger mode with step-by-step execution (requires single test) |

## Command Line Options

Expand Down Expand Up @@ -98,6 +99,7 @@ npx toolproof -c 20
| `--browser <IMPL>` | Specify which browser to use for tests (chrome or pagebrowse, default: chrome) |
| `--retry-count <COUNT>` | Number of times to retry failed tests before marking them as failed |
| `--failure-screenshot-location <DIR>` | If set, Toolproof will screenshot the browser to this location when a test fails |
| `--debugger` | Run in debugger mode with step-by-step execution (requires single test with --name) |

## Environment Variables

Expand All @@ -118,3 +120,4 @@ Most options can also be set using environment variables:
| `TOOLPROOF_SUPPORTED_VERSIONS` | Error if Toolproof does not match this version range |
| `TOOLPROOF_FAILURE_SCREENSHOT_LOCATION` | Location for browser screenshots on test failure |
| `TOOLPROOF_RETRY_COUNT` | Number of times to retry failed tests |
| `TOOLPROOF_DEBUGGER` | Run in debugger mode with step-by-step execution |
27 changes: 27 additions & 0 deletions docs/content/docs/debugger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: "Debugger Mode"
nav_title: "Debugger"
nav_section: Root
weight: 10
---

Toolproof's debugger mode allows you to run tests step-by-step, making it easier to understand test behavior, debug failures, and develop new tests.

## Enabling Debugger Mode

Run Toolproof with the `--debugger` flag along with a specific test:

```bash
npx toolproof --debugger --name "My Test Name"
```

Debugger mode requires running a single test. If you don't specify a test name, Toolproof will show an error.

When running in debugger mode:

- The browser runs with a visible window (not headless)
- Before each step executes, Toolproof pauses and shows you:
- The upcoming step to be executed
- The step's arguments (if any)
- The temporary directory path
- The server port (if a server is running)
12 changes: 9 additions & 3 deletions toolproof/src/definitions/browser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,18 @@ pub enum BrowserTester {
},
}

async fn try_launch_browser(mut max: usize) -> (Browser, chromiumoxide::Handler) {
async fn try_launch_browser(mut max: usize, visible: bool) -> (Browser, chromiumoxide::Handler) {
let mut launch = Err(CdpError::NotFound);
while launch.is_err() && max > 0 {
max -= 1;
let headless_mode = if visible {
chromiumoxide::browser::HeadlessMode::False
} else {
chromiumoxide::browser::HeadlessMode::New
};
launch = Browser::launch(
BrowserConfig::builder()
.headless_mode(chromiumoxide::browser::HeadlessMode::New)
.headless_mode(headless_mode)
.user_data_dir(tempdir().expect("testing on a system with a temp dir"))
.viewport(Some(Viewport {
width: 1600,
Expand Down Expand Up @@ -101,7 +106,8 @@ impl BrowserTester {
async fn initialize(params: &ToolproofParams) -> Self {
match params.browser {
crate::options::ToolproofBrowserImpl::Chrome => {
let (browser, mut handler) = try_launch_browser(3).await;
let visible = params.debugger;
let (browser, mut handler) = try_launch_browser(3, visible).await;

BrowserTester::Chrome {
browser: Arc::new(browser),
Expand Down
8 changes: 8 additions & 0 deletions toolproof/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,14 @@ async fn main_inner() -> Result<(), ()> {
RunMode::All
};

// Debugger mode requires running a single test
if universe.ctx.params.debugger && !matches!(run_mode, RunMode::One(_)) {
eprintln!(
"Debugger mode requires running a single test. Please specify a test using --name."
);
return Err(());
}

enum HoldingError {
TestFailure,
SnapFailure { out: String },
Expand Down
14 changes: 14 additions & 0 deletions toolproof/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ fn get_cli_matches() -> ArgMatches {
.required(false)
.value_parser(value_parser!(PathBuf)),
)
.arg(
arg!(
--debugger ... "Run in debugger mode with step-by-step execution"
)
.action(clap::ArgAction::SetTrue),
)
.get_matches()
}

Expand Down Expand Up @@ -240,6 +246,10 @@ pub struct ToolproofParams {
#[setting(env = "TOOLPROOF_RETRY_COUNT")]
#[setting(default = 0)]
pub retry_count: usize,

/// Run in debugger mode with step-by-step execution
#[setting(env = "TOOLPROOF_DEBUGGER")]
pub debugger: bool,
}

// The configuration object used internally
Expand Down Expand Up @@ -333,5 +343,9 @@ impl ToolproofParams {
if let Some(retry_count) = cli_matches.get_one::<usize>("retry-count") {
self.retry_count = *retry_count;
}

if cli_matches.get_flag("debugger") {
self.debugger = true;
}
}
}
45 changes: 45 additions & 0 deletions toolproof/src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use async_recursion::async_recursion;

use console::Term;
use normalize_path::NormalizePath;
use schematic::color::owo::OwoColorize;
use similar_string::find_best_similarity;
use std::{
collections::HashMap,
Expand Down Expand Up @@ -78,6 +80,37 @@ pub async fn run_toolproof_experiment(
res
}

fn debugger_pause(step: &ToolproofTestStep, civ: &Civilization) {
if !civ.universe.ctx.params.debugger {
return;
}

let term = Term::stdout();

println!("\n{}", "--- DEBUGGER ---".on_blue().bold());

if let Some(ref tmp_dir) = civ.tmp_dir {
println!("Temp directory: {}", tmp_dir.path().to_string_lossy());
}

if let Some(port) = civ.assigned_server_port {
println!("Server hosted at: http://localhost:{}", port);
}

println!("\nNext:");
println!(" - step: {}", step);
let args_pretty = step.args_pretty();
if !args_pretty.trim().is_empty() {
for line in args_pretty.lines() {
println!(" {}", line);
}
}

println!("\n{}", "Press [Enter] to continue...".dimmed());

let _ = term.read_line();
}

#[async_recursion]
async fn run_toolproof_steps(
file_directory: &String,
Expand Down Expand Up @@ -119,6 +152,8 @@ async fn run_toolproof_steps(
state,
platforms,
} => {
debugger_pause(&marked_base_step, civ);

let target_path = PathBuf::from(file_directory)
.join(other_file)
.normalize()
Expand Down Expand Up @@ -167,6 +202,8 @@ async fn run_toolproof_steps(
state,
platforms,
} => {
debugger_pause(&marked_base_step, civ);

let Some((reference_segments, defined_macro)) =
civ.universe.macros.get_key_value(step_macro)
else {
Expand Down Expand Up @@ -229,6 +266,8 @@ async fn run_toolproof_steps(
platforms,
..
} => {
debugger_pause(&marked_base_step, civ);

let Some((reference_segments, instruction)) =
civ.universe.instructions.get_key_value(step)
else {
Expand Down Expand Up @@ -274,6 +313,8 @@ async fn run_toolproof_steps(
platforms,
..
} => {
debugger_pause(&marked_base_step, civ);

let Some((reference_ret, retrieval_step)) =
civ.universe.retrievers.get_key_value(retrieval)
else {
Expand Down Expand Up @@ -354,6 +395,8 @@ async fn run_toolproof_steps(
state,
platforms,
} => {
debugger_pause(&marked_base_step, civ);

let Some((reference_ret, retrieval_step)) =
civ.universe.retrievers.get_key_value(snapshot)
else {
Expand Down Expand Up @@ -405,6 +448,8 @@ async fn run_toolproof_steps(
state,
platforms,
} => {
debugger_pause(&marked_base_step, civ);

let Some((reference_ret, retrieval_step)) =
civ.universe.retrievers.get_key_value(extract)
else {
Expand Down
Loading