Dolibarr Hurl Test Skill
SkillFiles & storageCreate and maintain Hurl tests for Dolibarr ERP/CRM. Use when asked to create, write, or fix hurl tests for Dolibarr, or when working with test/hurl directory. Covers Dolibarr-specific conventions for hurl testing including authentication, test file naming, and test structure.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Dolibarr Hurl Test Skill skill
What this skill tells your AI
The instructions your AI receives, as published by dolibarr/dolibarr in .agents/skills/skill-doli-test-hurl/SKILL.md and read by ahel’s review.
Description
Create and maintain Hurl tests for Dolibarr ERP/CRM. Use when asked to create, write, or fix hurl tests for Dolibarr, or when working with test/hurl directory. This skill covers the Dolibarr-specific conventions for hurl testing including authentication, test file naming, and test structure.
IMPORTANT: Add body not contains "xdebug-error" assertions to Hurl tests (and also for each {Error:,Warning:,notice,warning}).
NOTE: Hurl uses Rust regex syntax, which does not support negative lookaheads ((?!...)). Use character class exclusions like [^X] instead.
When to Use
- User asks to create hurl tests for Dolibarr
- User asks to fix failing hurl tests
- Working with test/hurl directory
- Need to test Dolibarr GUI or API endpoints
- User mentions "hurl test" or "hurl tests"
Relationship with AGENTS.md
The instructions in this file are complementary to the instructions defined in AGENTS.md.
AGENTS.mdcontains the general instructions and rules for the project.SKILLS.mdcontains additional instructions specific to skills.- Unless explicitly stated otherwise, the instructions from both files apply.
SKILLS.mddoes not replace or overrideAGENTS.md.- If an instruction in
SKILLS.mdconflicts withAGENTS.md, follow the rules defined byAGENTS.md.
Dolibarr Hurl Test Conventions
File Naming Pattern
Hurl test files must follow a specific naming pattern based on the number prefix:
| Prefix | Description | Authentication Required |
|---|---|---|
| 00 | No authentication required | No |
| 10 | Authentication required (GUI tests) | Yes |
| 20 | Reserved for future use | - |
| 30 | POST data for later tests | Yes |
| 40 | GET data that was POST'ed in 30_ files | Yes |
| 50 | PUT data that was POST'ed in 30_ files | Yes |
| 60 | GET data that was PUT'ed in 50_ files | Yes |
| 70 | DELETE data | Yes |
| 80 | GET data that was just DELETE'd | Yes |
| 90 | Reserved for future use | - |
Important: For Dolibarr GUI tests that require authentication, use the 10 prefix (not 30, 40, etc.). The run.sh script only executes GUI tests matching */10*.hurl.
File Structure
test/hurl/
├── api/ # API endpoint tests
│ ├── 00_*.hurl # No auth required
│ └── 10_*.hurl # Auth required
├── gui/ # GUI/browser tests
│ ├── 00_*.hurl # No auth required
│ ├── 10_*.hurl # Auth required (most GUI tests)
│ └── save_login_cookie.hurl # Special file for login
└── public/ # Public endpoint tests
└── 00_*.hurl
Directory Structure
Place tests in directories matching the Dolibarr module/path structure:
test/hurl/gui/fourn/commande/- Supplier order teststest/hurl/gui/societe/- Third party/company teststest/hurl/api/commande/- Customer order API teststest/hurl/api/fourn/- Supplier API tests
Authentication
Dolibarr requires authentication for most GUI operations. The hurl framework handles this through:
- save_login_cookie.hurl - Special file that logs in and saves cookies
- Cookie jar - Passed via
--cookiefileparameter - CSRF token - Captured from login page and passed with requests
Capturing CSRF Token
# Get the login page to capture CSRF token
GET http://{{hostnport}}/
HTTP 200
[Captures]
token: xpath "normalize-space(//meta[@name='anti-csrf-newtoken']/@content)"
# Use the token in subsequent requests
POST http://{{hostnport}}/some/page.php
[FormParams]
action: some_action
token: {{token}}
Creating Test Data
Supplier Creation
Suppliers (fournisseurs) are created via societe/card.php with type=f:
POST http://{{hostnport}}/societe/card.php?leftmenu=suppliers&action=create&type=f
HTTP 200
[FormParams]
name: Test Supplier {{timestamp}}
code_fournisseur: TEST_{{timestamp}}
fournisseur: 1
client: 0
email: test-{{timestamp}}@example.com
address:
zip:
town:
country_id: 1
token: {{token}}
# Capture the supplier ID
GET http://{{hostnport}}/societe/card.php?socid={{socid}}&leftmenu=suppliers
HTTP 200
[Captures]
socid: queryParam "socid"
Supplier Order Creation
POST http://{{hostnport}}/fourn/commande/card.php?action=create
HTTP 200
[FormParams]
socid: {{socid}}
date: {{now}}
date_livraison: {{now_plus_7_days}}
ref_supplier: ORDER-{{timestamp}}
statut: 0
token: {{token}}
# Capture the order ID
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Captures]
orderid: queryParam "id"
Customer Order Creation
POST http://{{hostnport}}/commande/card.php?action=create
HTTP 200
[FormParams]
socid: {{socid}}
date: {{now}}
date_livraison: {{now_plus_7_days}}
ref_customer: ORDER-{{timestamp}}
statut: 0
token: {{token}}
Assertions
Use Dolibarr-specific assertions:
# Check for tracking number display
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Asserts]
body contains "FX123456789012"
body contains "FedEx"
body contains "TrackingNumber"
body contains "https://www.fedex.com/tracking/FX123456789012"
Handling Expected Warnings
When testing for expected warning messages while catching unexpected ones:
# Expect a specific warning but catch others
GET http://{{hostnport}}/some/page.php
HTTP 200
[Asserts]
body contains "Warning: Invoice.*has different tracking"
body not contains "Warning: [^I]"
This allows the expected "Invoice..." warning while catching any other warnings that don't start with "I".
Regex Patterns
Hurl uses Rust regex syntax. Important differences from PCRE:
- No negative lookahead support:
(?!pattern)is NOT supported - Use character class exclusions:
[^X]to match any character except X - To match a literal
?in URLs, use[?]not\? - Start anchors: Use
^for start of string
# Match redirect Location header with query parameter
header "Location" matches "^/fourn/facture/card.php[?]id=[0-9]+$"
# Match any warning except those starting with "I"
body not contains "Warning: [^I]"
Running Tests
Using run_with_server.sh
The recommended way to run hurl tests locally:
# Run specific test path
test/hurl/run_with_server.sh gui/fourn/commande
# Run with custom port
test/hurl/run_with_server.sh --port=9000 gui/fourn/commande
Using run.sh Directly
# Set environment variables
export DOLIHOST="127.0.50.1"
export DOLIPORT="9999"
export DOLIUSERNAME="admin"
export DOLIPASSWORD="admin"
export COOKIEJAR="/tmp/hurl_cookie.jar"
# Run tests
test/hurl/run.sh --cookiefile="$COOKIEJAR" --port="$DOLIPORT" --host="$DOLIHOST" --user="$DOLIUSERNAME" --pass="$DOLIPASSWORD" gui/fourn/commande
Running Specific Tests
# Run all tests containing "tracking"
test/hurl/run_with_server.sh tracking
# Run tests in a specific directory
test/hurl/run_with_server.sh gui/fourn
# Run specific test file
test/hurl/run_with_server.sh 10_supplier_order_tracking_POST
Test File Template
GUI Test with Authentication (10 prefix)
# Test description
POST http://{{hostnport}}/module/page.php?action=create
HTTP 200
[FormParams]
field1: value1
field2: value2
token: {{token}}
# Capture created ID
GET http://{{hostnport}}/module/page.php?id={{created_id}}
HTTP 200
[Captures]
created_id: queryParam "id"
# Verify the creation
GET http://{{hostnport}}/module/page.php?id={{created_id}}
HTTP 200
[Asserts]
body contains "value1"
body contains "value2"
body not contains "warning"
body not contains "error"
body not contains "notice"
body not contains "Warning:"
body not contains "Error:"
body not contains "xdebug-error"
API Test with Authentication (10 prefix)
# API tests use the DOLAPIKEY header
GET http://{{hostnport}}/api/index.php/orders
HTTP 200
DOLAPIKEY: DOLAPIKEY: your_api_key_here
[Asserts]
jsonpath "$.data" exists
jsonpath "$.pagination.total" >= 0
body not contains "warning"
body not contains "error"
body not contains "notice"
body not contains "Warning:"
body not contains "Error:"
Built-in Variables
Hurl provides useful built-in variables:
| Variable | Description | Example |
|---|---|---|
| {{timestamp}} | Current timestamp | 1712345678 |
| {{now}} | Current date/time | 2024-04-05T12:34:56 |
| {{now_plus_7_days}} | Date 7 days from now | 2024-04-12T12:34:56 |
| {{hostnport}} | Host and port | 127.0.50.1:9999 |
| {{token}} | CSRF token (captured) | abc123... |
| {{username}} | Username | admin |
| {{password}} | Password | admin |
Best Practices
-
Always include token in POST requests
[FormParams] action: create token: {{token}} -
Use proper URL patterns
- Supplier creation:
societe/card.php?leftmenu=suppliers&action=create&type=f - Customer creation:
societe/card.php?leftmenu=customers&action=create&type=c - Supplier order:
fourn/commande/card.php - Customer order:
commande/card.php
- Supplier creation:
-
Capture IDs for cleanup Always capture created IDs so they can be used in subsequent tests and cleaned up:
[Captures] socid: queryParam "socid" orderid: queryParam "id" -
Clean up test data Create DELETE tests (70_ prefix) to remove test data:
POST http://{{hostnport}}/fourn/commande/card.php?action=delete HTTP 200 [FormParams] id: {{orderid}} token: {{token}} -
Use proper HTTP status codes
- 200 for successful operations
- 404 for deleted/not found resources
- 400 for bad requests
-
Test both positive and negative cases
- Test valid inputs
- Test invalid inputs
- Test edge cases
-
Keep tests small and focused Each test file should test one specific functionality.
-
Handle expected messages properly
- Use
body contains "Expected message"for messages you expect to see - Use
body not contains "Error:"andbody not contains "Warning:"for unexpected errors - For expected warnings, check for the specific warning text and use exclusion patterns for unexpected ones
- Use
Common Issues and Fixes
Issue: Tests not being found
Cause: Wrong file name prefix
Fix: Use 10_ prefix for GUI tests that require authentication
Issue: Authentication failing
Cause: Missing token or cookie
Fix: Ensure save_login_cookie.hurl runs first and token is captured
Issue: CSRF protection blocking requests
Cause: Missing token parameter
Fix: Add token: {{token}} to all POST requests with actions
Issue: Redirects not being followed
Cause: Hurl doesn't follow redirects by default Fix: Use separate GET requests to follow redirects, capture IDs from URLs
Issue: Regex patterns not working
Cause: Using PCRE syntax (like (?!...) negative lookahead) which Rust regex doesn't support
Fix: Use Rust-compatible regex syntax. For excluding patterns, use character class exclusions like [^X] instead of negative lookaheads
Issue: URL query parameter matching
Cause: Using \? to match literal ? in URLs
Fix: Use [?] to match literal question marks in regex patterns
Issue: Expected warnings being flagged as failures
Cause: Using body not contains "Warning:" when some warnings are expected
Fix: Use body contains "Warning: ExpectedMessage" for expected warnings and body not contains "Warning: [^E]" to catch unexpected warnings starting with other characters
Issue: Tests running against wrong environment
Cause: Hardcoded URLs
Fix: Use {{hostnport}} variable and proper environment setup
Example: Complete Test Suite for Supplier Order Tracking
File: test/hurl/gui/fourn/commande/10_supplier_order_tracking_POST.hurl
# Create a supplier for tracking test
POST http://{{hostnport}}/societe/card.php?leftmenu=suppliers&action=create&type=f
HTTP 200
[FormParams]
name: Test Supplier for Tracking {{timestamp}}
code_fournisseur: TRACKTEST_{{timestamp}}
fournisseur: 1
client: 0
email: test-supplier-{{timestamp}}@example.com
country_id: 1
token: {{token}}
# Capture the supplier ID
GET http://{{hostnport}}/societe/card.php?socid={{socid}}&leftmenu=suppliers
HTTP 200
[Captures]
socid: queryParam "socid"
# Create a supplier order
POST http://{{hostnport}}/fourn/commande/card.php?action=create
HTTP 200
[FormParams]
socid: {{socid}}
date: {{now}}
date_livraison: {{now_plus_7_days}}
ref_supplier: ORDER-{{timestamp}}
statut: 0
token: {{token}}
# Capture the order ID
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Captures]
orderid: queryParam "id"
# Set tracking information
POST http://{{hostnport}}/fourn/commande/card.php?action=set_tracking
HTTP 200
[FormParams]
id: {{orderid}}
tracking_awb: FX123456789012
carrier_code: FX
token: {{token}}
File: test/hurl/gui/fourn/commande/10_supplier_order_tracking_GET.hurl
# Verify tracking information is displayed
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 200
[Asserts]
body contains "FX123456789012"
body contains "FedEx"
body contains "TrackingNumber"
body contains "https://www.fedex.com/tracking/FX123456789012"
File: test/hurl/gui/fourn/commande/10_supplier_order_tracking_DELETE.hurl
# Delete the supplier order
POST http://{{hostnport}}/fourn/commande/card.php?action=delete
HTTP 200
[FormParams]
id: {{orderid}}
token: {{token}}
# Verify order is deleted
GET http://{{hostnport}}/fourn/commande/card.php?id={{orderid}}
HTTP 404
# Delete the supplier
POST http://{{hostnport}}/societe/card.php?leftmenu=suppliers&action=delete
HTTP 200
[FormParams]
socid: {{socid}}
token: {{token}}
# Verify supplier is deleted
GET http://{{hostnport}}/societe/card.php?socid={{socid}}&leftmenu=suppliers
HTTP 404
Validation
Before committing hurl tests:
- Run the tests locally with
test/hurl/run_with_server.sh <test_path> - Verify all tests pass
- Check that cleanup works (DELETE tests)
- Ensure no test data is left behind
References
Signals
- GitHub stars
- 8k
- Forks
- 4k
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
skill-doli-test-hurl- Source
- github.com/dolibarr/dolibarr