This file provides context for AI agents working with the HyperFleet E2E testing framework.
Black-box E2E testing framework for HyperFleet cluster lifecycle management. Built with Go, Ginkgo, and OpenAPI-generated clients. Tests create ephemeral clusters for complete isolation.
make buildBinary output: bin/hyperfleet-e2e
make checkThis runs: format check, vet, lint, and unit tests.
make fmt # Format code
make fmt-check # Verify formatting
make vet # Run go vet
make lint # Run golangci-lint
make test # Run unit testsRequired after OpenAPI schema updates:
make generateDownloads schema from hyperfleet-api and regenerates pkg/api/openapi/.
export HYPERFLEET_API_URL=https://api.hyperfleet.example.com
make build
./bin/hyperfleet-e2e test --label-filter=tier0make cleanBefore submitting changes, verify:
- Format:
make fmt - Generate:
make generate(if OpenAPI schema or config changed) - Lint:
make lint(must pass with zero errors) - Vet:
make vet(must pass) - Unit Tests:
make test(all tests must pass) - Build:
make build(binary must compile) - E2E Tests: Optional, but recommended for test changes
- Extension: Use
.goNOT_test.go - Location:
e2e/{resource-type}/descriptive-name.go - Package: Match directory name (e.g., package
clusterfore2e/cluster/) - Test Name: Format as
[Suite: component] Description(e.g.,[Suite: cluster] Create Cluster via API)
Example:
package cluster
var testName = "[Suite: cluster] Create Cluster via API"Every test MUST have exactly one severity label:
import "github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/labels"
var _ = ginkgo.Describe(testName,
ginkgo.Label(labels.Tier0), // Critical severity
func() { ... }
)Available labels:
- Severity (required):
Tier0,Tier1,Tier2 - Optional:
Negative,Performance,Upgrade,Disruptive,Slow
Required structure:
var _ = ginkgo.Describe(testName, ginkgo.Label(...), func() {
var h *helper.Helper
var resourceID string
ginkgo.BeforeEach(func() {
h = helper.New()
})
ginkgo.It("description", func(ctx context.Context) {
ginkgo.By("step description")
// test logic
})
ginkgo.AfterEach(func(ctx context.Context) {
if h == nil || resourceID == "" {
return
}
if err := h.CleanupTestCluster(ctx, resourceID); err != nil {
ginkgo.GinkgoWriter.Printf("Warning: cleanup failed: %v\n", err)
}
})
})Use ginkgo.By() for major steps ONLY. Do NOT use inside Eventually closures:
// CORRECT
ginkgo.By("waiting for cluster to become Ready")
err := h.WaitForClusterPhase(ctx, clusterID, openapi.Ready, timeout)
// INCORRECT - never do this
Eventually(func() {
ginkgo.By("checking status") // ❌ Wrong
// ...
}).Should(Succeed())Use Eventually with g.Expect() (not Expect()):
Eventually(func(g Gomega) {
cluster, err := h.Client.GetCluster(ctx, clusterID)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(cluster.Status.Phase).To(Equal(openapi.Ready))
}, timeout, pollInterval).Should(Succeed())ALWAYS implement cleanup in AfterEach:
ginkgo.AfterEach(func(ctx context.Context) {
if h == nil || clusterID == "" {
return
}
if err := h.CleanupTestCluster(ctx, clusterID); err != nil {
ginkgo.GinkgoWriter.Printf("Warning: failed to cleanup cluster %s: %v\n", clusterID, err)
}
})Test payloads in testdata/payloads/ support Go templates for dynamic values:
{
"name": "cluster-{{.Random}}",
"labels": {
"created-at": "{{.Timestamp}}"
}
}Available variables: .Random, .Timestamp. See pkg/client/payload.go.
- Modify generated code: Never edit files in
pkg/api/openapi/- these are generated bymake generate - Use
_test.gosuffix: Test files must use.goextension - Hardcode timeouts: Use
h.Cfg.Timeouts.*values from config - Skip cleanup: Always implement
AfterEachcleanup - Commit without checks: Always run
make checkbefore committing - Use
ginkgo.By()inEventually: Only use at top-level test steps - Import test packages: Do NOT import
e2e/*packages in production code - Edit OpenAPI schema: Schema is maintained in hyperfleet-api repo
- Use helper functions: Prefer
h.WaitForClusterPhase()over manual polling - Use config values:
h.Cfg.Timeouts.*for timeouts,h.Cfg.Polling.*for intervals - Store resource IDs: Save IDs in variables for cleanup
- Check errors: Use
Expect(err).NotTo(HaveOccurred()) - Use context: All test functions receive
context.Context
- Create file:
e2e/{suite}/descriptive-name.go - Copy structure from existing test
- Update test name, labels, and logic
- Run validation checklist
- Test locally before submitting PR
When hyperfleet-api changes:
# Default (from main branch)
make generate
# From specific branch
OPENAPI_SPEC_REF=release-0.2 make generate
# Verify changes compile
make build# Build and run specific tests
make build
./bin/hyperfleet-e2e test --focus "\[Suite: cluster\]"
# Run critical tests only
./bin/hyperfleet-e2e test --label-filter=tier0
# Debug mode
./bin/hyperfleet-e2e test --log-level=debugPriority (highest to lowest):
- CLI flags:
--api-url,--log-level - Environment variables:
HYPERFLEET_API_URL - Config file:
configs/config.yaml - Built-in defaults
cluster, err := h.Client.CreateClusterFromPayload(ctx, "testdata/payloads/clusters/cluster-request.json")
Expect(err).NotTo(HaveOccurred())
clusterID = *cluster.Iderr = h.WaitForClusterPhase(ctx, clusterID, openapi.Ready, h.Cfg.Timeouts.Cluster.Ready)
Expect(err).NotTo(HaveOccurred())statuses, err := h.Client.GetClusterStatuses(ctx, clusterID)
Expect(err).NotTo(HaveOccurred())
for _, adapter := range statuses.Items {
hasApplied := h.HasCondition(adapter.Conditions, client.ConditionTypeApplied, openapi.True)
Expect(hasApplied).To(BeTrue())
}- Getting Started - Run first test in 10 minutes
- Architecture - Framework design
- Development - Detailed test writing guide
- CONTRIBUTING.md - Contribution guidelines