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:
- Pending: Job is queued for execution
- Running: Job is actively executing steps
- Success: All steps completed successfully
- Failed: One or more steps failed
- Skipped: Job was skipped due to conditions
- 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 == 200Conditional 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 == 200Step 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 timeoutStep 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_login2. 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
}} bytes3. 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 == 200Job 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 == 2002. 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 == trueBest 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 steps2. 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 context3. 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:
- Actions - Learn about the action system and available plugins
- Expressions and Templates - Master dynamic configuration and testing
- 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.