This document provides guidelines and information for developers.
htmltest is a fast HTML validation and link checker written in Go. It's designed as a faster alternative to the Ruby-based html-proofer tool.
- Validates HTML files for broken links, images, and script references
- Checks internal and external links
- Verifies image alt attributes
- Tests meta refresh tags
- Checks for valid favicon references
- Validates DOCTYPE declarations
- Caches external link checks for faster subsequent runs
On sites with 2000+ files, htmltest runs in seconds compared to minutes with other tools, making it ideal for CI/CD pipelines.
- Go 1.13 or higher (check with
go version) - Git
- golangci-lint (optional, for linting)
-
Clone the repository:
git clone https://github.com/wjdp/htmltest.git cd htmltest -
Install dependencies:
go mod download
Or using Make:
make deps
-
Build the project:
make build
Or directly:
go build -o bin/htmltest main.go
-
Run tests:
make test-race
htmltest/
├── main.go # Entry point
├── htmldoc/ # HTML document parsing and storage
│ ├── document.go # Document representation
│ ├── reference.go # Reference (link/image/script) handling
│ └── attr.go # HTML attribute utilities
├── htmltest/ # Core testing logic
│ ├── htmltest.go # Main test orchestration
│ ├── options.go # Configuration options
│ ├── check-*.go # Specific checkers (links, images, etc.)
│ └── fixtures/ # Test fixtures
├── issues/ # Issue tracking and reporting
├── refcache/ # External reference caching
└── output/ # Output formatting and logging
Run all tests across all packages:
make testCatches concurrency issues:
make test-raceRun tests with race detection AND coverage (matches GitHub Actions CI):
make test-ciWhen developing features, use the TDD targets for fast iteration:
# Run specific test with clean cache (shows cache state after)
make test-tdd-cache TEST_RUN=TestTimeoutIsCached
# Run specific test (no cache inspection, faster)
make test-tdd TEST_RUN=TestTimeoutIsCached
# Run multiple tests matching a pattern
make test-tdd TEST_RUN='.*Cache.*'
# Just clean the cache
make clean-cacheThe test-tdd targets:
- Automatically clean the refcache before running tests
- Support pattern matching to run multiple related tests
test-tdd-cacheshows the cache state after tests complete- Continue even if tests fail (good for RED phase of TDD)
TDD Workflow Example:
-
Write/update your test first (RED phase):
make test-tdd-cache TEST_RUN=TestNewFeature # Test fails, shows what's in cache -
Implement the feature (GREEN phase):
make test-tdd-cache TEST_RUN=TestNewFeature # Test passes, verify cache state -
Run related tests to check for regressions:
make test-tdd TEST_RUN='.*Cache.*'
go test ./htmldoc
go test ./htmltest
go test ./issues
go test ./refcachemake test-coverageView HTML coverage report:
go tool cover -html=coverage.txtmake test-benchgo test -v -run TestMissingOptions ./htmltestTests use fixture files located in */fixtures/ directories:
- HTML test files with various scenarios (broken links, valid links, etc.)
- Configuration files
- Sample resources (images, scripts)
External HTTP requests are mocked using VCR cassettes (recorded HTTP interactions) in htmltest/fixtures/vcr/*.cassette. This allows tests to run offline and consistently without hitting real external URLs.
- Test files:
*_test.go - Test functions:
Test<Feature>(t *testing.T) - Benchmark functions:
Benchmark<Feature>(b *testing.B) - Helper functions:
t<Helper>()(lowercase t prefix)
make buildThis creates bin/htmltest with version information from git tags.
go build -ldflags "-X main.version=$(git describe --tags)" -o bin/htmltest main.gomake installmake fmtCheck if code is properly formatted without modifying files (fails if not formatted):
make fmt-checkmake vetRequires golangci-lint:
make lintRun format check, vet, and tests exactly as CI does:
make check
# or
make ciThis is the best command to run before committing to ensure CI will pass.
After building, test the binary manually:
# Show help
./bin/htmltest -h
# Test a single file
./bin/htmltest htmltest/fixtures/links/head_link_href.html
# Test a directory
./bin/htmltest htmldoc/fixtures/documents/
# Test with config
./bin/htmltest -c htmldoc/fixtures/conf.yamlWhen adding new features:
- Add appropriate test fixtures in
htmltest/fixtures/ - Create test cases in the relevant
*_test.gofile - Use the test helper functions (see
test_helpers_test.go) - Ensure tests pass both locally and don't require external network access
Example test pattern:
func TestNewFeature(t *testing.T) {
hT := tTestFileOpts("fixtures/feature/test.html",
map[string]interface{}{"NewOption": true})
tExpectIssueCount(t, hT, 0)
}Run make help to see all available commands:
# Build
make build # Build the binary
make build-verify # Build and verify with smoke tests
make install # Install to GOPATH/bin
# Testing
make test # Run all tests
make test-race # Run tests with race detector (recommended)
make test-coverage # Generate coverage report
make test-ci # Run tests exactly as CI does (race + coverage)
make test-bench # Run benchmarks
# TDD Workflow (recommended during development)
make test-tdd # Run specific test(s) with clean cache
make test-tdd-cache # Same but shows cache state after
make clean-cache # Just remove refcache file
# Code Quality
make fmt # Format code
make fmt-check # Check formatting (CI mode - fails if not formatted)
make vet # Run go vet
make lint # Run linter (requires golangci-lint)
make check # Run all CI checks locally (fmt-check, vet, test-ci)
make ci # Alias for check
# Utilities
make clean # Remove build artifacts and cache
make deps # Download dependencies
make help # Show all commands with examplesSee make help for TDD workflow examples and more details.
- Run all CI checks locally:
make checkormake ci- This runs format checking, vet, and tests with race detection + coverage
- Ensures your code will pass CI
- Update documentation if needed
- Add tests for new features
If you need to fix formatting issues, run make fmt before running checks again.
- Create a descriptive branch name (e.g.,
feature/check-canonical-links) - Write clear commit messages
- Include tests for new functionality
- Update README.md if adding user-facing features
- Ensure all CI checks pass
- Issues: Submit an issue
- Documentation: Check the README for user documentation
- Code patterns: Look at existing tests and checkers for examples
- Follow standard Go conventions
- Use
go fmtfor formatting - Write tests for new features
- Keep functions focused and small
- Document exported functions and types
- Use descriptive variable names
By contributing to htmltest, you agree that your contributions will be licensed under the same license as the project (see LICENCE).
Thank you for contributing to htmltest! 🎉