Dolibarr Hurl Test Skill

SkillFiles & storage

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. 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.

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.md contains the general instructions and rules for the project.
  • SKILLS.md contains additional instructions specific to skills.
  • Unless explicitly stated otherwise, the instructions from both files apply.
  • SKILLS.md does not replace or override AGENTS.md.
  • If an instruction in SKILLS.md conflicts with AGENTS.md, follow the rules defined by AGENTS.md.

Dolibarr Hurl Test Conventions

File Naming Pattern

Hurl test files must follow a specific naming pattern based on the number prefix:

PrefixDescriptionAuthentication Required
00No authentication requiredNo
10Authentication required (GUI tests)Yes
20Reserved for future use-
30POST data for later testsYes
40GET data that was POST'ed in 30_ filesYes
50PUT data that was POST'ed in 30_ filesYes
60GET data that was PUT'ed in 50_ filesYes
70DELETE dataYes
80GET data that was just DELETE'dYes
90Reserved 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 tests
  • test/hurl/gui/societe/ - Third party/company tests
  • test/hurl/api/commande/ - Customer order API tests
  • test/hurl/api/fourn/ - Supplier API tests

Authentication

Dolibarr requires authentication for most GUI operations. The hurl framework handles this through:

  1. save_login_cookie.hurl - Special file that logs in and saves cookies
  2. Cookie jar - Passed via --cookiefile parameter
  3. 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:

VariableDescriptionExample
{{timestamp}}Current timestamp1712345678
{{now}}Current date/time2024-04-05T12:34:56
{{now_plus_7_days}}Date 7 days from now2024-04-12T12:34:56
{{hostnport}}Host and port127.0.50.1:9999
{{token}}CSRF token (captured)abc123...
{{username}}Usernameadmin
{{password}}Passwordadmin

Best Practices

  1. Always include token in POST requests

    [FormParams]
    action: create
    token: {{token}}
    
  2. 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
  3. 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"
    
  4. 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}}
    
  5. Use proper HTTP status codes

    • 200 for successful operations
    • 404 for deleted/not found resources
    • 400 for bad requests
  6. Test both positive and negative cases

    • Test valid inputs
    • Test invalid inputs
    • Test edge cases
  7. Keep tests small and focused Each test file should test one specific functionality.

  8. Handle expected messages properly

    • Use body contains "Expected message" for messages you expect to see
    • Use body not contains "Error:" and body not contains "Warning:" for unexpected errors
    • For expected warnings, check for the specific warning text and use exclusion patterns for unexpected ones

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:

  1. Run the tests locally with test/hurl/run_with_server.sh <test_path>
  2. Verify all tests pass
  3. Check that cleanup works (DELETE tests)
  4. 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