e2e

package
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 9, 2026 License: MIT Imports: 8 Imported by: 0

README

End-to-End (E2E) Tests

This directory contains end-to-end tests that verify glab commands against a real GitLab instance. Unlike integration tests which use mock API servers, E2E tests exercise the complete system including actual GitLab API interactions.

Overview

E2E tests provide the highest level of confidence that glab works correctly with real GitLab instances. These tests:

  • Run against a real GitLab instance (gitlab.com or self-hosted)
  • Require valid GitLab authentication
  • Create and cleanup real resources (MRs, branches, etc.)
  • Are skipped by default and only run when explicitly enabled
  • Are slower than unit and integration tests

Key characteristics:

  • Tests run against real GitLab API (not mocks)
  • Require GLAB_E2E_TEST=true to enable
  • Require GITLAB_TOKEN for authentication
  • Require GLAB_E2E_PROJECT pointing to a test project
  • Slower execution (seconds per test)
  • May have quota/rate limiting considerations

When to Use E2E Tests vs Integration Tests

Aspect Integration Tests E2E Tests
API Mock HTTP server Real GitLab instance
Speed Fast (milliseconds) Slow (seconds)
Dependencies None GitLab instance, auth, test project
CI Run on every PR Run nightly or on-demand
Purpose Verify command logic Verify real API compatibility
Cost Free May hit rate limits

General guidance:

  • Write integration tests for most command testing
  • Write E2E tests for critical flows and API compatibility verification
  • E2E tests should be a small subset of your test suite

Setup

Required Environment Variables

E2E tests require the following environment variables:

# Enable E2E tests (required)
export GLAB_E2E_TEST=true

# GitLab personal access token with API scope (required)
export GITLAB_TOKEN=glpat-your-token-here

# Test project in format "owner/repo" (required)
export GLAB_E2E_PROJECT=your-username/test-project

# GitLab hostname (optional, defaults to gitlab.com)
export GLAB_E2E_HOST=gitlab.com
Creating a Test Project

E2E tests need a GitLab project to run against. This project should:

  1. Be dedicated to testing - don't use a production project
  2. Have minimal value - tests may create/delete branches and MRs
  3. Be accessible - your token must have appropriate permissions

Create a test project:

# Using glab (if already set up)
glab repo create glab-e2e-test --public

# Or via GitLab web UI:
# 1. Go to https://gitlab.com/projects/new
# 2. Create project "glab-e2e-test"
# 3. Note the project path (e.g., "yourusername/glab-e2e-test")

Set the project path:

export GLAB_E2E_PROJECT=yourusername/glab-e2e-test
Getting a GitLab Token

E2E tests require a GitLab personal access token:

  1. Go to https://gitlab.com/-/profile/personal_access_tokens
  2. Click "Add new token"
  3. Name: "glab-e2e-tests"
  4. Scopes: Select api (full API access)
  5. Expiration: Set a reasonable date
  6. Click "Create personal access token"
  7. Copy the token immediately (it won't be shown again)
export GITLAB_TOKEN=glpat-your-token-here

Security note: Never commit tokens to version control. Use environment variables or a secure credential manager.

Running E2E Tests

Run All E2E Tests
# With environment variables set
GLAB_E2E_TEST=true \
GITLAB_TOKEN=glpat-your-token \
GLAB_E2E_PROJECT=yourusername/glab-e2e-test \
go test -v ./tests/e2e/...
Run Specific E2E Test
GLAB_E2E_TEST=true \
GITLAB_TOKEN=glpat-your-token \
GLAB_E2E_PROJECT=yourusername/glab-e2e-test \
go test -v ./tests/e2e -run TestE2E_AuthStatus
Skip E2E Tests (Default Behavior)

By default, E2E tests are skipped:

# E2E tests will be skipped with message:
# "Skipping E2E test (set GLAB_E2E_TEST=true to run)"
go test -v ./tests/e2e/...

Writing E2E Tests

Basic E2E Test Structure
package e2e_test

import (
	"strings"
	"testing"

	"github.com/PhilipKram/gitlab-cli/cmd"
	"github.com/PhilipKram/gitlab-cli/tests/e2e"
)

func TestE2E_AuthStatus(t *testing.T) {
	// 1. Skip if E2E not enabled
	e2e.SkipIfNoE2E(t)

	// 2. Create E2E test factory (real API client)
	tf := e2e.NewE2ETestFactory(t)

	// 3. Execute command
	authCmd := cmd.NewAuthCmd(tf.Factory)
	authCmd.SetArgs([]string{"status"})
	authCmd.SetOut(tf.IO.Out)
	authCmd.SetErr(tf.IO.ErrOut)

	err := authCmd.Execute()
	if err != nil {
		t.Fatalf("command failed: %v", err)
	}

	// 4. Assert output from real GitLab
	output := tf.IO.String()
	if !strings.Contains(output, tf.Host) {
		t.Errorf("expected output to contain host %q", tf.Host)
	}
}
E2E Test with Resource Cleanup
func TestE2E_MRWorkflow(t *testing.T) {
	e2e.SkipIfNoE2E(t)

	tf := e2e.NewE2ETestFactory(t)
	client := e2e.GetRealAPIClient(t)

	// Track resources for cleanup
	cleanup := e2e.NewCleanupTracker(t, client, tf.Owner+"/"+tf.Repo)

	// Generate unique test branch name
	branchName := e2e.GenerateTestName("mr-test")
	cleanup.TrackBranch(branchName)

	// Create test branch (in real test, use git or API)
	// ...

	// Create MR
	mrCmd := cmd.NewMRCmd(tf.Factory)
	mrCmd.SetArgs([]string{
		"create",
		"--title", "E2E Test MR",
		"--source-branch", branchName,
		"--target-branch", "main",
	})

	err := mrCmd.Execute()
	if err != nil {
		t.Fatalf("failed to create MR: %v", err)
	}

	// Parse MR IID from output and track for cleanup
	output := tf.IO.String()
	// ... parse MR IID ...
	// cleanup.TrackMergeRequest(mrIID)

	// Verify MR was created
	if !strings.Contains(output, "E2E Test MR") {
		t.Error("MR title not found in output")
	}

	// Cleanup happens automatically via t.Cleanup()
}
Testing Eventually-Consistent Operations

Some GitLab operations are eventually consistent (e.g., pipelines starting). Use polling helpers:

func TestE2E_PipelineRun(t *testing.T) {
	e2e.SkipIfNoE2E(t)

	tf := e2e.NewE2ETestFactory(t)

	// Run pipeline
	pipelineCmd := cmd.NewPipelineCmd(tf.Factory)
	pipelineCmd.SetArgs([]string{"run"})

	err := pipelineCmd.Execute()
	if err != nil {
		t.Fatalf("failed to run pipeline: %v", err)
	}

	// Wait for pipeline to start (with timeout)
	e2e.AssertEventuallyTrue(t, 30*time.Second, func() bool {
		listCmd := cmd.NewPipelineCmd(tf.Factory)
		listCmd.SetArgs([]string{"list", "--limit", "1"})

		tf.IO.Out.Reset()
		err := listCmd.Execute()
		if err != nil {
			return false
		}

		output := tf.IO.String()
		return strings.Contains(output, "running") || strings.Contains(output, "success")
	})
}
Custom Cleanup Handlers

For complex cleanup scenarios:

func TestE2E_CustomCleanup(t *testing.T) {
	e2e.SkipIfNoE2E(t)

	cleanup := e2e.NewCleanupTracker(t, client, projectPath)

	// Add custom cleanup logic
	cleanup.AddCleanupHandler(func() error {
		// Custom cleanup code
		// Return error if cleanup fails (will be logged)
		return nil
	})

	// ... test code ...
}

E2E Test Helpers Reference

Core Functions
Function Purpose
SkipIfNoE2E(t) Skip test unless GLAB_E2E_TEST=true
GetTestGitLabHost() Get GitLab hostname for tests (default: gitlab.com)
GetTestProjectPath() Get test project owner/repo from GLAB_E2E_PROJECT
NewE2ETestFactory(t) Create factory with real API client
GetRealAPIClient(t) Create real GitLab API client
VerifyE2ESetup(t) Verify all E2E requirements are met
Resource Management
Function Purpose
NewCleanupTracker(t, client, project) Track resources for cleanup
TrackMergeRequest(iid) Mark MR for cleanup
TrackBranch(name) Mark branch for cleanup
AddCleanupHandler(func) Add custom cleanup function
GenerateTestName(prefix) Create unique timestamped name
Polling & Assertions
Function Purpose
WaitForResource(t, timeout, interval, checkFunc) Poll until resource ready
AssertEventuallyTrue(t, timeout, assertion) Retry assertion until success

Best Practices

1. Always Use SkipIfNoE2E

Always start E2E tests with e2e.SkipIfNoE2E(t):

func TestE2E_Something(t *testing.T) {
	e2e.SkipIfNoE2E(t)
	// ... rest of test
}

This ensures tests are skipped unless explicitly enabled.

2. Clean Up Resources

Use CleanupTracker to ensure resources are deleted even if tests fail:

cleanup := e2e.NewCleanupTracker(t, client, projectPath)
cleanup.TrackMergeRequest(mrIID)
cleanup.TrackBranch(branchName)
// Cleanup happens automatically
3. Use Unique Names

Generate unique names to avoid conflicts with concurrent tests:

branchName := e2e.GenerateTestName("feature")
// e.g., "e2e-test-feature-1709571234"
4. Handle Rate Limits

Be mindful of GitLab rate limits:

// Add delays between API-heavy operations if needed
time.Sleep(1 * time.Second)

// Or use exponential backoff for retries
5. Verify Setup Early

Use VerifyE2ESetup at the start of test suites:

func TestMain(m *testing.M) {
	// This will fail fast if setup is incorrect
	// when E2E tests are enabled
	code := m.Run()
	os.Exit(code)
}
6. Keep E2E Tests Minimal

Don't duplicate integration test coverage. Focus on:

  • Critical user workflows
  • Real API compatibility verification
  • Features that are hard to mock accurately
7. Document Prerequisites

If tests require specific project setup:

func TestE2E_ProtectedBranch(t *testing.T) {
	e2e.SkipIfNoE2E(t)

	// Document requirements in test or skip message
	t.Log("This test requires 'main' branch to be protected")

	// ... test code ...
}

Testing Against Self-Hosted GitLab

E2E tests support self-hosted GitLab instances:

export GLAB_E2E_HOST=gitlab.example.com
export GITLAB_TOKEN=your-self-hosted-token
export GLAB_E2E_PROJECT=team/project
export GLAB_E2E_TEST=true

go test -v ./tests/e2e/...

Note: Ensure your self-hosted instance:

  • Is accessible from your test environment
  • Has API enabled
  • Has a test project available
  • Your token has appropriate scopes

Troubleshooting

Tests Skip Even With GLAB_E2E_TEST=true

Check that GLAB_E2E_TEST is exactly "true":

echo $GLAB_E2E_TEST  # Should print: true
"GITLAB_TOKEN environment variable required"

Ensure token is exported:

echo $GITLAB_TOKEN  # Should print your token
"GLAB_E2E_PROJECT environment variable not set"

Set the test project path:

export GLAB_E2E_PROJECT=yourusername/your-test-project
"invalid GLAB_E2E_PROJECT format"

Project path must be in owner/repo format:

# ✅ Correct
export GLAB_E2E_PROJECT=myusername/my-repo

# ❌ Wrong
export GLAB_E2E_PROJECT=my-repo
export GLAB_E2E_PROJECT=https://gitlab.com/myusername/my-repo
Rate Limiting Errors

If you hit rate limits:

  1. Add delays between tests: time.Sleep(time.Second)
  2. Reduce number of concurrent E2E tests
  3. Use a GitLab instance with higher rate limits
  4. Implement exponential backoff in test helpers

CI/CD Integration

GitHub Actions Example
name: E2E Tests (Nightly)

on:
  schedule:
    - cron: '0 2 * * *'  # Run at 2 AM daily
  workflow_dispatch:  # Allow manual trigger

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-go@v4
        with:
          go-version: '1.21'

      - name: Run E2E Tests
        env:
          GLAB_E2E_TEST: true
          GITLAB_TOKEN: ${{ secrets.GITLAB_E2E_TOKEN }}
          GLAB_E2E_PROJECT: glab-org/e2e-test-project
        run: go test -v ./tests/e2e/...
GitLab CI Example
e2e-tests:
  stage: test
  only:
    - schedules  # Run on scheduled pipelines only
  script:
    - export GLAB_E2E_TEST=true
    - export GITLAB_TOKEN=$E2E_GITLAB_TOKEN
    - export GLAB_E2E_PROJECT=glab-org/e2e-test-project
    - go test -v ./tests/e2e/...
  when: manual  # Or remove for automatic runs

Example Test Scenarios

1. Auth Flow
func TestE2E_AuthStatus(t *testing.T) {
	e2e.SkipIfNoE2E(t)
	tf := e2e.NewE2ETestFactory(t)

	// Verify auth status against real GitLab
	// ... test implementation ...
}
2. MR Workflow
func TestE2E_MRListAndView(t *testing.T) {
	e2e.SkipIfNoE2E(t)
	tf := e2e.NewE2ETestFactory(t)

	// List real MRs in test project
	// View a specific MR
	// Verify output format
}
3. Pipeline Workflow
func TestE2E_PipelineList(t *testing.T) {
	e2e.SkipIfNoE2E(t)
	tf := e2e.NewE2ETestFactory(t)

	// List real pipelines
	// Verify output contains expected data
}

Summary

  • E2E tests run against real GitLab instances
  • Skipped by default - require GLAB_E2E_TEST=true
  • Use sparingly - complement integration tests, don't replace them
  • Clean up resources - use CleanupTracker
  • Run in CI - on nightly schedules or manually

For most testing, prefer integration tests (tests/integration/). Use E2E tests for critical flows and API compatibility verification.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertEventuallyTrue

func AssertEventuallyTrue(t *testing.T, timeout time.Duration, assertion func() bool)

AssertEventuallyTrue retries an assertion until it passes or times out. Useful for eventually-consistent operations.

Example:

AssertEventuallyTrue(t, 10*time.Second, func() bool {
    output := runCommand(t, "mr", "list")
    return strings.Contains(output, "my-test-mr")
})

func GenerateTestName

func GenerateTestName(prefix string) string

GenerateTestName creates a unique test resource name with timestamp. This helps identify and cleanup test resources.

func GetRealAPIClient

func GetRealAPIClient(t *testing.T) *api.Client

GetRealAPIClient creates a real GitLab API client for E2E tests. This client will make actual HTTP requests to the GitLab instance.

func GetTestGitLabHost

func GetTestGitLabHost() string

GetTestGitLabHost returns the GitLab hostname to use for E2E tests. Defaults to "gitlab.com" but can be overridden with GLAB_E2E_HOST.

This allows testing against self-hosted GitLab instances.

func GetTestProjectPath

func GetTestProjectPath() (owner, repo string, err error)

GetTestProjectPath returns the project path to use for E2E tests. This should be in the format "owner/repo", e.g., "gitlab-org/gitlab-test".

The project must exist and the authenticated user must have access to it. Set via GLAB_E2E_PROJECT environment variable.

func SkipIfNoE2E

func SkipIfNoE2E(t *testing.T)

SkipIfNoE2E skips the test unless GLAB_E2E_TEST environment variable is set to "true". E2E tests run against a real GitLab instance and require proper authentication.

Usage:

func TestE2E_AuthStatus(t *testing.T) {
    e2e.SkipIfNoE2E(t)
    // ... test code that runs against real GitLab
}

func VerifyE2ESetup

func VerifyE2ESetup(t *testing.T)

VerifyE2ESetup checks that all required environment variables are set and the GitLab instance is accessible.

func WaitForResource

func WaitForResource(t *testing.T, timeout, interval time.Duration, checkFunc func() (bool, error)) error

WaitForResource polls until a resource becomes available or times out. This is useful for eventually-consistent operations like pipeline creation.

Example:

err := WaitForResource(t, 30*time.Second, 1*time.Second, func() (bool, error) {
    pipeline, err := client.GetPipeline(projectID, pipelineID)
    if err != nil {
        return false, err
    }
    return pipeline.Status != "pending", nil
})

Types

type CleanupTracker

type CleanupTracker struct {
	// contains filtered or unexported fields
}

CleanupTestResources removes any resources created during E2E tests. This should be called via t.Cleanup() to ensure resources are removed even if tests fail.

Resources tracked for cleanup: - Merge requests created with test prefix - Branches created with test prefix - Comments added during tests

func NewCleanupTracker

func NewCleanupTracker(t *testing.T, client *api.Client, projectPath string) *CleanupTracker

NewCleanupTracker creates a tracker for E2E test resources.

func (*CleanupTracker) AddCleanupHandler

func (ct *CleanupTracker) AddCleanupHandler(handler func() error)

AddCleanupHandler registers a custom cleanup function.

func (*CleanupTracker) Cleanup

func (ct *CleanupTracker) Cleanup()

Cleanup performs all registered cleanup operations. This is called automatically via t.Cleanup().

func (*CleanupTracker) TrackBranch

func (ct *CleanupTracker) TrackBranch(branchName string)

TrackBranch adds a branch to be deleted after the test.

func (*CleanupTracker) TrackMergeRequest

func (ct *CleanupTracker) TrackMergeRequest(iid int)

TrackMergeRequest adds a merge request to be closed after the test.

type E2ETestFactory

type E2ETestFactory struct {
	*cmdtest.TestFactory
	Host  string
	Owner string
	Repo  string
}

E2ETestFactory creates a test factory configured for E2E testing. Unlike integration tests, this factory uses real GitLab API clients.

func NewE2ETestFactory

func NewE2ETestFactory(t *testing.T) *E2ETestFactory

NewE2ETestFactory creates a new factory for E2E tests with real API client.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL