Skip to Content
GuideConceptsJobs and Steps

Jobs and Steps

Jobs and steps are the building blocks of Probe workflows. Understanding their mechanics, execution model, and interaction patterns is crucial for building effective automation. This guide explores the detailed behavior and advanced use cases.

Job Fundamentals

A job is a logical grouping of related steps that execute together as a unit. Jobs provide:

  • Isolation: Each job runs in its own context
  • Parallelism: Jobs can run simultaneously unless dependencies exist
  • State Management: Jobs track their execution state and results
  • Output Sharing: Jobs can produce outputs for other jobs to consume

Job Structure

jobs: - id: job-id name: Human Readable Name # Optional: Display name needs: [other-job] # Optional: Job dependencies timeout: 300s # Optional: Job timeout steps: # Required: Array of steps # Step definitions...

Job Lifecycle

Jobs progress through several states during execution:

  1. Pending: Job is queued for execution
  2. Running: Job is actively executing steps
  3. Success: All steps completed successfully
  4. Failed: One or more steps failed
  5. Skipped: Job was skipped due to conditions
  6. Cancelled: Job was cancelled due to timeout or error

Job Dependencies

Use the needs keyword to create execution dependencies:

jobs: - id: setup name: Environment Setup steps: - name: Initialize Database id: setup uses: http with: method: GET url: "{{vars.DB_API}}/init" test: res.code == 200 outputs: db_session_id: res.body.session_id - id: test-suite-a name: API Test Suite A needs: [setup] # Wait for setup to complete steps: - name: Test User API uses: http with: method: GET url: "{{vars.API_URL}}/users" headers: X-Session-ID: "{{outputs.setup.db_session_id}}" test: res.code == 200 - id: test-suite-b name: API Test Suite B needs: [setup] # Also depends on setup steps: - name: Test Order API uses: http with: method: GET url: "{{vars.API_URL}}/orders" headers: X-Session-ID: "{{outputs.setup.db_session_id}}" test: res.code == 200 - id: cleanup name: Environment Cleanup needs: [test-suite-a, test-suite-b] # Wait for both test suites steps: - name: Clean Database uses: http with: method: GET url: "{{vars.DB_API}}/cleanup" headers: X-Session-ID: "{{outputs.setup.db_session_id}}" test: res.code == 200

Conditional Job Execution

A job runs unless its skipif expression is true. The expression can read vars and the outputs published by the jobs it depends on.

Note that a failing job skips everything that depends on it. To branch on an outcome, publish the outcome as an output instead of asserting it with test.

jobs: - id: health-check name: Basic Health Check steps: - name: Ping Service id: ping uses: http with: method: GET url: "{{vars.service_url}}/ping" outputs: service_responsive: res.code == 200 - name: Detailed Health Check needs: [health-check] skipif: "!outputs.ping.service_responsive" steps: - name: Deep Health Check uses: http with: method: GET url: "{{vars.service_url}}/health/detailed" test: res.code == 200 - name: Send Notification needs: [health-check] skipif: outputs.ping.service_responsive steps: - name: Alert Team uses: hello echo: "{{vars.service_url}} did not respond to ping"

A job can also be skipped on configuration alone:

- name: Production smoke test skipif: vars.environment != "production" steps: - name: Check uses: http with: method: GET url: "{{vars.service_url}}/health" test: res.code == 200

Step Fundamentals

A step is the smallest unit of execution in Probe. Each step performs a specific action and can:

  • Execute actions (HTTP requests, commands, etc.)
  • Test results with assertions
  • Produce outputs for other steps
  • Echo messages to the console
  • Execute conditionally

Step Structure

steps: - name: Step Name # Required: Descriptive name id: step-id # Optional: Unique identifier for referencing action: http # Optional: Action to execute with: # Optional: Action parameters url: https://api.example.com method: GET test: res.code == 200 # Optional: Test condition outputs: # Optional: Data to pass to other steps response_time: (rt.sec * 1000) user_count: res.body.total_users echo: "Message" # Optional: Display message timeout: 30s # Optional: Step timeout

Step Types

1. Action Steps

Execute specific actions like HTTP requests:

- name: Check User API uses: http with: url: "{{vars.API_URL}}/users/{{vars.TEST_USER_ID}}" method: GET headers: Authorization: "Bearer {{vars.API_TOKEN}}" Accept: "application/json" test: res.code == 200 && res.body.user.active == true outputs: user_id: res.body.user.id user_email: res.body.user.email last_login: res.body.user.last_login

2. Echo Steps

Display messages or computed values:

- name: Display Results echo: | Test Results Summary: User ID: {{outputs['previous-step'].user_id}} Email: {{outputs['previous-step'].user_email}} Last Login: {{outputs['previous-step'].last_login}} Response Time: {{outputs['previous-step'].response_time}}ms Test Completed: {{unixtime()}}

3. Hybrid Steps

Combine actions with echo messages:

- name: Test and Report uses: http with: method: GET url: "{{vars.API_URL}}/status" test: res.code == 200 echo: | API Status Check: Status Code: {{res.status}} Response Time: {{rt.duration}} API Version: {{res.body.version}}

Step Execution Flow

Steps within a job execute sequentially by default:

jobs: - id: sequential-test name: Sequential Step Execution steps: - name: Step 1 - Setup id: setup uses: http with: method: GET url: "{{vars.API_URL}}/setup" test: res.code == 200 outputs: session_id: res.body.session_id - name: Step 2 - Execute Test id: test uses: http with: method: GET url: "{{vars.API_URL}}/test" headers: X-Session-ID: "{{outputs.setup.session_id}}" test: res.code == 200 outputs: test_result: res.body.result - name: Step 3 - Cleanup uses: http with: method: GET url: "{{vars.API_URL}}/cleanup" headers: X-Session-ID: "{{outputs.setup.session_id}}" test: res.code == 200 - name: Step 4 - Report uses: hello echo: "Test completed with result: {{outputs.test.test_result}}"

Conditional Step Execution

A step is skipped when its skipif expression is true. The expression sees the same context as test, including the outputs of earlier steps.

steps: - name: Primary Health Check id: primary uses: http with: method: GET url: "{{vars.primary_url}}/health" outputs: primary_healthy: res.code == 200 - name: Backup Service Check id: backup uses: http skipif: outputs.primary.primary_healthy with: method: GET url: "{{vars.backup_url}}/health" outputs: backup_healthy: res.code == 200 - name: Report uses: hello echo: | Primary: {{outputs.primary.primary_healthy ? "Online" : "Offline"}} Backup: {{outputs.backup_healthy ?? "not checked"}}

Advanced Patterns

1. Error Recovery Patterns

Implement robust error handling with recovery steps:

jobs: - id: resilient-check name: Resilient Service Check steps: - name: Attempt Primary Connection id: primary-attempt uses: http timeout: 10s with: method: GET url: "{{vars.SERVICE_URL}}/api/v1/health" test: res.code == 200 outputs: primary_success: res.code == 200 - name: Try Alternative Endpoint id: alt-attempt uses: http timeout: 15s with: method: GET url: "{{vars.SERVICE_URL}}/api/v2/health" test: res.code == 200 outputs: alt_success: res.code == 200 - name: Fallback to Legacy Endpoint id: legacy-attempt uses: http timeout: 20s with: method: GET url: "{{vars.SERVICE_URL}}/health" test: res.code == 200 outputs: legacy_success: res.code == 200 - name: Final Status Report uses: hello echo: | Service Health Check Results: Primary API (v1): {{outputs['primary-attempt'].primary_success ? "✅" : "❌"}} Alternative API (v2): {{outputs['alt-attempt'].alt_success ? "✅" : "❌"}} Legacy API: {{outputs['legacy-attempt'].legacy_success ? "✅" : "❌"}} Overall Status: {{ outputs['primary-attempt'].primary_success || outputs['alt-attempt'].alt_success || outputs['legacy-attempt'].legacy_success ? "HEALTHY" : "DOWN" }}

2. Data Collection and Aggregation

Collect data across multiple steps for analysis:

jobs: - id: performance-analysis name: Performance Analysis steps: - name: Test Homepage id: homepage uses: http with: method: GET url: "{{vars.BASE_URL}}/" test: res.code == 200 outputs: homepage_time: (rt.sec * 1000) homepage_size: res.body_size - name: Test API Endpoint id: api uses: http with: method: GET url: "{{vars.BASE_URL}}/api/users" test: res.code == 200 outputs: api_time: (rt.sec * 1000) api_size: res.body_size - name: Test Search Function id: search uses: http with: method: GET url: "{{vars.BASE_URL}}/search?q=test" test: res.code == 200 outputs: search_time: (rt.sec * 1000) search_size: res.body_size - name: Performance Summary uses: hello echo: | Performance Analysis Results: Homepage: Response Time: {{outputs.homepage.homepage_time}}ms Size: {{outputs.homepage.homepage_size}} bytes API Endpoint: Response Time: {{outputs.api.api_time}}ms Size: {{outputs.api.api_size}} bytes Search Function: Response Time: {{outputs.search.search_time}}ms Size: {{outputs.search.search_size}} bytes Average Response Time: {{ (outputs.homepage.homepage_time + outputs.api.api_time + outputs.search.search_time) / 3 }}ms Total Data Transfer: {{ outputs.homepage.homepage_size + outputs.api.api_size + outputs.search.search_size }} bytes

3. Dynamic Step Configuration

Configure steps based on runtime conditions:

jobs: - id: adaptive-monitoring name: Adaptive Monitoring steps: - name: Determine Environment id: env-detect uses: http with: method: GET url: "{{vars.SERVICE_URL}}/config" test: res.code == 200 outputs: environment: res.body.environment feature_flags: res.body.features monitoring_level: res.body.monitoring.level - name: Basic Health Check uses: http with: method: GET url: "{{vars.SERVICE_URL}}/health" test: res.code == 200 - name: Detailed Monitoring id: adaptive-monitoring uses: http with: method: GET url: "{{vars.SERVICE_URL}}/metrics" test: res.code == 200 outputs: cpu_usage: res.body.system.cpu_percent memory_usage: res.body.system.memory_percent - name: Feature-Specific Tests uses: http with: method: GET url: "{{vars.SERVICE_URL}}/beta/features" test: res.code == 200 - name: Production Alerts uses: hello echo: | 🚨 PRODUCTION ALERT: High resource usage detected! CPU: {{outputs.detailed.cpu_usage}}% Memory: {{outputs.detailed.memory_usage}}%

Step and Job Identification

Step IDs

Use id to reference steps from other parts of the workflow:

steps: - name: User Authentication Test id: auth-test # Define ID for referencing uses: http with: url: "{{vars.API_URL}}/auth/login" method: POST body: | { "username": "testuser", "password": "{{vars.TEST_PASSWORD}}" } test: res.code == 200 outputs: auth_token: res.body.token user_id: res.body.user.id - name: User Profile Test uses: http with: method: GET url: "{{vars.API_URL}}/users/{{outputs['auth-test'].user_id}}" # Reference by ID headers: Authorization: "Bearer {{outputs['auth-test'].auth_token}}" # Reference by ID test: res.code == 200

Job References

Reference job results from other jobs:

jobs: - id: database-check name: Database Connectivity steps: - name: Test Database uses: http with: method: GET url: "{{vars.DB_API}}/ping" test: res.code == 200 - id: api-check name: API Functionality needs: [database-check] steps: - name: Test API uses: http with: method: GET url: "{{vars.API_URL}}/health" test: res.code == 200 - name: Skip Message uses: hello echo: "Skipping API test due to database connectivity issues"

Performance Optimization

1. Parallel Job Execution

Structure jobs to run in parallel when possible:

jobs: # These jobs can run in parallel (no dependencies) - id: frontend-test name: Frontend Tests steps: - name: Test UI Components uses: http with: method: GET url: "{{vars.FRONTEND_URL}}" test: res.code == 200 - id: backend-test name: Backend Tests steps: - name: Test API Endpoints uses: http with: method: GET url: "{{vars.BACKEND_URL}}/api" test: res.code == 200 - id: database-test name: Database Tests steps: - name: Test Database Connection uses: http with: method: GET url: "{{vars.DB_URL}}/health" test: res.code == 200 # This job waits for all parallel jobs to complete - id: integration-test name: Integration Tests needs: [frontend-test, backend-test, database-test] steps: - name: End-to-End Test uses: http with: method: GET url: "{{vars.APP_URL}}/integration-test" test: res.code == 200

2. Efficient Resource Usage

Optimize step execution for better resource utilization:

jobs: - id: efficient-monitoring name: Efficient Resource Monitoring steps: # Use timeouts to prevent hanging - name: Quick Health Check uses: http timeout: 5s # Short timeout for ping with: method: GET url: "{{vars.SERVICE_URL}}/ping" test: res.code == 200 # Conditional expensive operations - name: Detailed Analysis uses: http timeout: 30s # Longer timeout for detailed analysis with: method: GET url: "{{vars.SERVICE_URL}}/detailed-metrics" test: res.code == 200 # Batch related operations - name: Batch Status Check uses: http with: url: "{{vars.SERVICE_URL}}/batch-status" method: POST body: | { "checks": [ {"type": "health", "endpoint": "/health"}, {"type": "metrics", "endpoint": "/metrics"}, {"type": "version", "endpoint": "/version"} ] } test: res.code == 200 && res.body.all_passed == true

Best Practices

1. Job Granularity

Strike the right balance in job size:

# Good: Focused, cohesive jobs jobs: - id: authentication-tests name: Authentication System Tests steps: - name: Test Login - name: Test Logout - name: Test Token Refresh - name: Test Password Reset - id: user-management-tests name: User Management Tests steps: - name: Test User Creation - name: Test User Update - name: Test User Deletion # Avoid: Overly granular jobs jobs: - id: test-login name: test-login steps: - name: Test Login - id: test-logout name: test-logout steps: - name: Test Logout # Avoid: Monolithic jobs jobs: - id: all-tests name: all-tests steps: - name: Test Login - name: Test Database - name: Test Email - name: Test Files # ... 50 more unrelated steps

2. Clear Step Names

Use descriptive, action-oriented step names:

steps: # Good: Clear, specific names - name: Verify User Registration API Returns 201 - name: Test Database Connection Pool Health - name: Validate JWT Token Expiration Logic - name: Check Email Service Rate Limiting # Avoid: Vague or generic names - name: Test API # Too vague - name: Check Thing # Not descriptive - name: Step 1 # No context

3. Proper Error Handling

Implement appropriate error handling strategies:

steps: # Critical step - fail fast - name: Verify Database Connectivity uses: http with: method: GET url: "{{vars.DB_URL}}/ping" test: res.code == 200 # Non-critical step - continue on failure - name: Update Usage Analytics uses: http with: method: GET url: "{{vars.ANALYTICS_URL}}/update" test: res.code == 200 # Recovery step - name: Log Failure Details uses: hello echo: "Analytics update failed, but continuing with main workflow"

What’s Next?

Now that you understand jobs and steps in detail, explore:

  1. Actions - Learn about the action system and available plugins
  2. Expressions and Templates - Master dynamic configuration and testing
  3. Data Flow - Understand how data moves through workflows

Jobs and steps are the execution engine of Probe. Master these concepts to build efficient, reliable, and maintainable automation workflows.

Updated at