Repository: kyverno/chainsaw
Pull request #285 (2023-10-31 16:42:03)

test : runner/logging package
Fix : https://github.com/kyverno/chainsaw/issues/270

shubham-cmyk: Fix : https://github.com/kyverno/chainsaw/issues/270

Patch:
diff --git a/.crds/chainsaw.kyverno.io_configurations.yaml b/.crds/chainsaw.kyverno.io_configurations.yaml
index 03b1f92d..6ab06c92 100644
--- a/.crds/chainsaw.kyverno.io_configurations.yaml
+++ b/.crds/chainsaw.kyverno.io_configurations.yaml
@@ -89,11 +89,29 @@ spec:
                 items:
                   type: string
                 type: array
-              timeout:
-                default: 30s
-                description: Global timeout configuration. Applies to all tests/test
+              timeouts:
+                description: Global timeouts configuration. Applies to all tests/test
                   steps if not overridden.
-                type: string
+                properties:
+                  apply:
+                    description: Apply defines the timeout for the apply operation
+                    type: string
+                  assert:
+                    description: Assert defines the timeout for the assert operation
+                    type: string
+                  cleanup:
+                    description: Cleanup defines the timeout for the cleanup operation
+                    type: string
+                  delete:
+                    description: Delete defines the timeout for the delete operation
+                    type: string
+                  error:
+                    description: Error defines the timeout for the error operation
+                    type: string
+                  exec:
+                    description: Exec defines the timeout for exec operations
+                    type: string
+                type: object
             type: object
         required:
         - spec
diff --git a/.release-notes/main.md b/.release-notes/main.md
index fb478bad..69b9c472 100644
--- a/.release-notes/main.md
+++ b/.release-notes/main.md
@@ -14,6 +14,9 @@ Release notes for `TODO`.
 ## :wrench: Fixes :wrench:
 
 ## :books: Docs :books:
+-->
 
 ## :guitar: Misc :guitar:
--->
+
+- Added a lot of unit tests
+- Improved error messages in a couple of places
diff --git a/README.md b/README.md
index baf40dcc..5273b2a3 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@
 [![License: Apache-2.0](https://img.shields.io/github/license/kyverno/chainsaw?color=blue)](https://github.com/kyverno/chainsaw/blob/main/LICENSE)
 [![codecov](https://codecov.io/gh/kyverno/chainsaw/branch/main/graph/badge.svg)](https://app.codecov.io/gh/kyverno/chainsaw/branch/main)
 
+<a href="https://kyverno.github.io/chainsaw" rel="https://kyverno.github.io/chainsaw">![logo](website/docs/static/kyverno-chainsaw-horizontal.png)</a>
+
 Chainsaw provides a declarative approach to test [Kubernetes](https://kubernetes.io) operators and controllers.
 
 While Chainsaw is designed for testing operators and controllers, it can declaratively test any kubernetes objects.
diff --git a/pkg/apis/v1alpha1/configuration_spec.go b/pkg/apis/v1alpha1/configuration_spec.go
index dc9fa8e9..f80bcb49 100644
--- a/pkg/apis/v1alpha1/configuration_spec.go
+++ b/pkg/apis/v1alpha1/configuration_spec.go
@@ -1,9 +1,5 @@
 package v1alpha1
 
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
 type ReportFormatType string
 
 const (
@@ -14,10 +10,9 @@ const (
 
 // ConfigurationSpec contains the configuration used to run tests.
 type ConfigurationSpec struct {
-	// Global timeout configuration. Applies to all tests/test steps if not overridden.
+	// Global timeouts configuration. Applies to all tests/test steps if not overridden.
 	// +optional
-	// +kubebuilder:default:="30s"
-	Timeout *metav1.Duration `json:"timeout,omitempty"`
+	Timeouts Timeouts `json:"timeouts"`
 
 	// Directories containing test cases to run.
 	// +optional
diff --git a/pkg/apis/v1alpha1/timeouts.go b/pkg/apis/v1alpha1/timeouts.go
new file mode 100644
index 00000000..4ab2e81f
--- /dev/null
+++ b/pkg/apis/v1alpha1/timeouts.go
@@ -0,0 +1,26 @@
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// Timeouts contains timeouts per operation.
+type Timeouts struct {
+	// Apply defines the timeout for the apply operation
+	Apply *metav1.Duration `json:"apply,omitempty"`
+
+	// Assert defines the timeout for the assert operation
+	Assert *metav1.Duration `json:"assert,omitempty"`
+
+	// Error defines the timeout for the error operation
+	Error *metav1.Duration `json:"error,omitempty"`
+
+	// Delete defines the timeout for the delete operation
+	Delete *metav1.Duration `json:"delete,omitempty"`
+
+	// Cleanup defines the timeout for the cleanup operation
+	Cleanup *metav1.Duration `json:"cleanup,omitempty"`
+
+	// Exec defines the timeout for exec operations
+	Exec *metav1.Duration `json:"exec,omitempty"`
+}
diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
index 3552ae01..7df38265 100644
--- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
@@ -152,11 +152,7 @@ func (in *Configuration) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) {
 	*out = *in
-	if in.Timeout != nil {
-		in, out := &in.Timeout, &out.Timeout
-		*out = new(v1.Duration)
-		**out = **in
-	}
+	in.Timeouts.DeepCopyInto(&out.Timeouts)
 	if in.TestDirs != nil {
 		in, out := &in.TestDirs, &out.TestDirs
 		*out = make([]string, len(*in))
@@ -447,11 +443,7 @@ func (in *Test) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TestSpec) DeepCopyInto(out *TestSpec) {
 	*out = *in
-	if in.Timeout != nil {
-		in, out := &in.Timeout, &out.Timeout
-		*out = new(v1.Duration)
-		**out = **in
-	}
+	in.Timeouts.DeepCopyInto(&out.Timeouts)
 	if in.Skip != nil {
 		in, out := &in.Skip, &out.Skip
 		*out = new(bool)
@@ -534,11 +526,7 @@ func (in *TestStep) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TestStepSpec) DeepCopyInto(out *TestStepSpec) {
 	*out = *in
-	if in.Timeout != nil {
-		in, out := &in.Timeout, &out.Timeout
-		*out = new(v1.Duration)
-		**out = **in
-	}
+	in.Timeouts.DeepCopyInto(&out.Timeouts)
 	if in.SkipDelete != nil {
 		in, out := &in.SkipDelete, &out.SkipDelete
 		*out = new(bool)
@@ -598,3 +586,49 @@ func (in *TestStepSpec) DeepCopy() *TestStepSpec {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Timeouts) DeepCopyInto(out *Timeouts) {
+	*out = *in
+	if in.Apply != nil {
+		in, out := &in.Apply, &out.Apply
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.Assert != nil {
+		in, out := &in.Assert, &out.Assert
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.Error != nil {
+		in, out := &in.Error, &out.Error
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.Delete != nil {
+		in, out := &in.Delete, &out.Delete
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.Cleanup != nil {
+		in, out := &in.Cleanup, &out.Cleanup
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.Exec != nil {
+		in, out := &in.Exec, &out.Exec
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Timeouts.
+func (in *Timeouts) DeepCopy() *Timeouts {
+	if in == nil {
+		return nil
+	}
+	out := new(Timeouts)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/pkg/commands/kuttl/migrate/command.go b/pkg/commands/kuttl/migrate/command.go
index 7b3da115..0c16a9b0 100644
--- a/pkg/commands/kuttl/migrate/command.go
+++ b/pkg/commands/kuttl/migrate/command.go
@@ -147,11 +147,17 @@ func testSuite(in unstructured.Unstructured) (*v1alpha1.Configuration, error) {
 	if err != nil {
 		return nil, err
 	}
-	timeout := &metav1.Duration{
-		Duration: time.Second * 30,
-	}
+	var timeouts v1alpha1.Timeouts
 	if from.Timeout != 0 {
-		timeout.Duration = time.Second * time.Duration(from.Timeout)
+		d := metav1.Duration{Duration: time.Second * time.Duration(from.Timeout)}
+		timeouts = v1alpha1.Timeouts{
+			Apply:   &d,
+			Assert:  &d,
+			Error:   &d,
+			Delete:  &d,
+			Cleanup: &d,
+			Exec:    &d,
+		}
 	}
 	to := &v1alpha1.Configuration{
 		TypeMeta: metav1.TypeMeta{
@@ -160,7 +166,7 @@ func testSuite(in unstructured.Unstructured) (*v1alpha1.Configuration, error) {
 		},
 		ObjectMeta: from.ObjectMeta,
 		Spec: v1alpha1.ConfigurationSpec{
-			Timeout:      timeout,
+			Timeouts:     timeouts,
 			TestDirs:     from.TestDirs,
 			SkipDelete:   from.SkipDelete,
 			ReportFormat: v1alpha1.ReportFormatType(from.ReportFormat),
diff --git a/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml b/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml
index 03b1f92d..6ab06c92 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_configurations.yaml
@@ -89,11 +89,29 @@ spec:
                 items:
                   type: string
                 type: array
-              timeout:
-                default: 30s
-                description: Global timeout configuration. Applies to all tests/test
+              timeouts:
+                description: Global timeouts configuration. Applies to all tests/test
                   steps if not overridden.
-                type: string
+                properties:
+                  apply:
+                    description: Apply defines the timeout for the apply operation
+                    type: string
+                  assert:
+                    description: Assert defines the timeout for the assert operation
+                    type: string
+                  cleanup:
+                    description: Cleanup defines the timeout for the cleanup operation
+                    type: string
+                  delete:
+                    description: Delete defines the timeout for the delete operation
+                    type: string
+                  error:
+                    description: Error defines the timeout for the error operation
+                    type: string
+                  exec:
+                    description: Exec defines the timeout for exec operations
+                    type: string
+                type: object
             type: object
         required:
         - spec
diff --git a/pkg/runner/logging/l.go b/pkg/runner/logging/l.go
index a12dc0e5..6598e777 100644
--- a/pkg/runner/logging/l.go
+++ b/pkg/runner/logging/l.go
@@ -2,7 +2,6 @@ package logging
 
 import (
 	"fmt"
-	"testing"
 
 	"github.com/kyverno/chainsaw/pkg/client"
 	"github.com/kyverno/kyverno/ext/output/color"
@@ -13,14 +12,14 @@ import (
 const eraser = "\b\b\b\b\b\b\b\b\b"
 
 type logger struct {
-	t        *testing.T
+	t        LoggerInterface
 	clock    clock.PassiveClock
 	test     string
 	step     string
 	resource ctrlclient.Object
 }
 
-func NewLogger(t *testing.T, clock clock.PassiveClock, test string, step string) Logger {
+func NewLogger(t LoggerInterface, clock clock.PassiveClock, test string, step string) Logger {
 	t.Helper()
 	return &logger{
 		t:     t,
diff --git a/pkg/runner/logging/logger.go b/pkg/runner/logging/logger.go
index f8947490..7133873f 100644
--- a/pkg/runner/logging/logger.go
+++ b/pkg/runner/logging/logger.go
@@ -9,3 +9,8 @@ type Logger interface {
 	Log(string, *color.Color, ...interface{})
 	WithResource(ctrlclient.Object) Logger
 }
+
+type LoggerInterface interface {
+	Log(args ...interface{})
+	Helper()
+}
diff --git a/pkg/runner/logging/mock.go b/pkg/runner/logging/mock.go
new file mode 100644
index 00000000..4df797ef
--- /dev/null
+++ b/pkg/runner/logging/mock.go
@@ -0,0 +1,55 @@
+package logging
+
+import (
+	"time"
+
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// test resource for testing
+type testResource struct {
+	ctrlclient.Object
+	name      string
+	namespace string
+	gvk       schema.GroupVersionKind
+}
+
+func (f *testResource) GetName() string {
+	return f.name
+}
+
+func (f *testResource) GetNamespace() string {
+	return f.namespace
+}
+
+func (f *testResource) GetObjectKind() schema.ObjectKind {
+	return &objectKind{
+		gvk: f.gvk,
+	}
+}
+
+type objectKind struct {
+	gvk schema.GroupVersionKind
+}
+
+func (o *objectKind) GroupVersionKind() schema.GroupVersionKind {
+	return o.gvk
+}
+
+func (o *objectKind) SetGroupVersionKind(kind schema.GroupVersionKind) {
+	o.gvk = kind
+}
+
+// This is a mock clock for testing purposes
+type mockClock struct {
+	time time.Time
+}
+
+func (m *mockClock) Now() time.Time {
+	return m.time
+}
+
+func (m *mockClock) Since(time.Time) time.Duration {
+	return 0
+}
diff --git a/pkg/runner/step.go b/pkg/runner/step.go
index f870bf17..556c63d9 100644
--- a/pkg/runner/step.go
+++ b/pkg/runner/step.go
@@ -49,27 +49,29 @@ func executeStep(t *testing.T, logger logging.Logger, ctx Context, basePath stri
 			})
 		}
 	}()
-	stepCtx, cancel := timeoutCtx(config, test, step.Spec)
-	defer cancel()
 	for _, operation := range step.Spec.Delete {
-		var resource unstructured.Unstructured
-		resource.SetAPIVersion(operation.APIVersion)
-		resource.SetKind(operation.Kind)
-		resource.SetName(operation.Name)
-		resource.SetNamespace(operation.Namespace)
-		resource.SetLabels(operation.Labels)
-		if err := ctx.namespacer.Apply(&resource); err != nil {
-			fail(t, operation.ContinueOnError)
-		}
-		if err := operations.Delete(stepCtx, logger, &resource, c); err != nil {
-			fail(t, operation.ContinueOnError)
-		}
+		func() {
+			var resource unstructured.Unstructured
+			resource.SetAPIVersion(operation.APIVersion)
+			resource.SetKind(operation.Kind)
+			resource.SetName(operation.Name)
+			resource.SetNamespace(operation.Namespace)
+			resource.SetLabels(operation.Labels)
+			if err := ctx.namespacer.Apply(&resource); err != nil {
+				fail(t, operation.ContinueOnError)
+			}
+			operationCtx, cancel := timeoutCtx(defaultDeleteTimeout, config.Timeouts.Delete, test.Timeouts.Delete, step.Spec.Timeouts.Delete, nil)
+			defer cancel()
+			if err := operations.Delete(operationCtx, logger, &resource, c); err != nil {
+				fail(t, operation.ContinueOnError)
+			}
+		}()
 	}
 	var cleanup operations.CleanupFunc
 	if skip := skipDelete(config, test, step.Spec); skip == nil || !*skip {
 		cleanup = func(obj ctrlclient.Object, c client.Client) {
 			t.Cleanup(func() {
-				cleanupCtx, cancel := timeoutCtx(config, test, step.Spec)
+				cleanupCtx, cancel := timeoutCtx(defaultCleanupTimeout, config.Timeouts.Cleanup, test.Timeouts.Cleanup, step.Spec.Timeouts.Cleanup, nil)
 				defer cancel()
 				if err := operations.Delete(cleanupCtx, logger, obj, c); err != nil {
 					t.Fail()
@@ -79,63 +81,75 @@ func executeStep(t *testing.T, logger logging.Logger, ctx Context, basePath stri
 	}
 	for _, operation := range step.Spec.Exec {
 		func() {
-			cmdCtx, cancel := timeoutExecCtx(operation.Exec, config, test, step.Spec)
+			operationCtx, cancel := timeoutCtx(defaultExecTimeout, config.Timeouts.Exec, test.Timeouts.Exec, step.Spec.Timeouts.Exec, operation.Timeout)
 			defer cancel()
-			if err := operations.Exec(cmdCtx, logger, operation.Exec, !operation.SkipLogOutput, ctx.namespacer.GetNamespace()); err != nil {
+			if err := operations.Exec(operationCtx, logger, operation.Exec, !operation.SkipLogOutput, ctx.namespacer.GetNamespace()); err != nil {
 				fail(t, operation.ContinueOnError)
 			}
 		}()
 	}
 	for _, operation := range step.Spec.Apply {
-		resources, err := resource.Load(filepath.Join(basePath, operation.File))
-		if err != nil {
-			logger.Log("LOAD  ", color.BoldRed, err)
-			fail(t, operation.ContinueOnError)
-		}
-		shouldFail := operation.ShouldFail != nil && *operation.ShouldFail
-		for i := range resources {
-			resource := &resources[i]
-			if err := ctx.namespacer.Apply(resource); err != nil {
+		func() {
+			resources, err := resource.Load(filepath.Join(basePath, operation.File))
+			if err != nil {
 				logger.Log("LOAD  ", color.BoldRed, err)
 				fail(t, operation.ContinueOnError)
 			}
-			if err := operations.Apply(stepCtx, logger, resource, c, shouldFail, cleanup); err != nil {
-				fail(t, operation.ContinueOnError)
+			shouldFail := operation.ShouldFail != nil && *operation.ShouldFail
+			for i := range resources {
+				resource := &resources[i]
+				if err := ctx.namespacer.Apply(resource); err != nil {
+					logger.Log("LOAD  ", color.BoldRed, err)
+					fail(t, operation.ContinueOnError)
+				}
+				operationCtx, cancel := timeoutCtx(defaultApplyTimeout, config.Timeouts.Apply, test.Timeouts.Apply, step.Spec.Timeouts.Apply, nil)
+				defer cancel()
+				if err := operations.Apply(operationCtx, logger, resource, c, shouldFail, cleanup); err != nil {
+					fail(t, operation.ContinueOnError)
+				}
 			}
-		}
+		}()
 	}
 	for _, operation := range step.Spec.Assert {
-		resources, err := resource.Load(filepath.Join(basePath, operation.File))
-		if err != nil {
-			logger.Log("LOAD  ", color.BoldRed, err)
-			fail(t, operation.ContinueOnError)
-		}
-		for i := range resources {
-			resource := &resources[i]
-			if err := ctx.namespacer.Apply(resource); err != nil {
+		func() {
+			resources, err := resource.Load(filepath.Join(basePath, operation.File))
+			if err != nil {
 				logger.Log("LOAD  ", color.BoldRed, err)
 				fail(t, operation.ContinueOnError)
 			}
-			if err := operations.Assert(stepCtx, logger, resources[i], c); err != nil {
-				fail(t, operation.ContinueOnError)
+			for i := range resources {
+				resource := &resources[i]
+				if err := ctx.namespacer.Apply(resource); err != nil {
+					logger.Log("LOAD  ", color.BoldRed, err)
+					fail(t, operation.ContinueOnError)
+				}
+				operationCtx, cancel := timeoutCtx(defaultAssertTimeout, config.Timeouts.Assert, test.Timeouts.Assert, step.Spec.Timeouts.Assert, nil)
+				defer cancel()
+				if err := operations.Assert(operationCtx, logger, resources[i], c); err != nil {
+					fail(t, operation.ContinueOnError)
+				}
 			}
-		}
+		}()
 	}
 	for _, operation := range step.Spec.Error {
-		resources, err := resource.Load(filepath.Join(basePath, operation.File))
-		if err != nil {
-			logger.Log("LOAD  ", color.BoldRed, err)
-			fail(t, operation.ContinueOnError)
-		}
-		for i := range resources {
-			resource := &resources[i]
-			if err := ctx.namespacer.Apply(resource); err != nil {
+		func() {
+			resources, err := resource.Load(filepath.Join(basePath, operation.File))
+			if err != nil {
 				logger.Log("LOAD  ", color.BoldRed, err)
 				fail(t, operation.ContinueOnError)
 			}
-			if err := operations.Error(stepCtx, logger, resources[i], c); err != nil {
-				fail(t, operation.ContinueOnError)
+			for i := range resources {
+				resource := &resources[i]
+				if err := ctx.namespacer.Apply(resource); err != nil {
+					logger.Log("LOAD  ", color.BoldRed, err)
+					fail(t, operation.ContinueOnError)
+				}
+				operationCtx, cancel := timeoutCtx(defaultErrorTimeout, config.Timeouts.Error, test.Timeouts.Error, step.Spec.Timeouts.Error, nil)
+				defer cancel()
+				if err := operations.Error(operationCtx, logger, resources[i], c); err != nil {
+					fail(t, operation.ContinueOnError)
+				}
 			}
-		}
+		}()
 	}
 }
diff --git a/pkg/runner/timeout.go b/pkg/runner/timeout.go
index 6cd0c844..c930ce65 100644
--- a/pkg/runner/timeout.go
+++ b/pkg/runner/timeout.go
@@ -4,36 +4,35 @@ import (
 	"context"
 	"time"
 
-	"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-func timeout(config v1alpha1.ConfigurationSpec, test v1alpha1.TestSpec, step v1alpha1.TestStepSpec) *time.Duration {
-	if step.Timeout != nil {
-		return &step.Timeout.Duration
+const (
+	defaultApplyTimeout   = 5 * time.Second
+	defaultAssertTimeout  = 30 * time.Second
+	defaultErrorTimeout   = 30 * time.Second
+	defaultDeleteTimeout  = 15 * time.Second
+	defaultCleanupTimeout = 30 * time.Second
+	defaultExecTimeout    = 5 * time.Second
+)
+
+func timeout(fallback time.Duration, config *metav1.Duration, test *metav1.Duration, step *metav1.Duration, operation *metav1.Duration) time.Duration {
+	if operation != nil {
+		return operation.Duration
 	}
-	if test.Timeout != nil {
-		return &test.Timeout.Duration
+	if step != nil {
+		return step.Duration
 	}
-	if config.Timeout != nil {
-		return &config.Timeout.Duration
+	if test != nil {
+		return test.Duration
 	}
-	return nil
-}
-
-func cancelNoOp() {}
-
-func timeoutCtx(config v1alpha1.ConfigurationSpec, test v1alpha1.TestSpec, step v1alpha1.TestStepSpec) (context.Context, context.CancelFunc) {
-	ctx := context.Background()
-	if timeout := timeout(config, test, step); timeout != nil {
-		return context.WithTimeout(ctx, *timeout)
+	if config != nil {
+		return config.Duration
 	}
-	return ctx, cancelNoOp
+	return fallback
 }
 
-func timeoutExecCtx(exec v1alpha1.Exec, config v1alpha1.ConfigurationSpec, test v1alpha1.TestSpec, step v1alpha1.TestStepSpec) (context.Context, context.CancelFunc) {
-	if exec.Timeout != nil && exec.Timeout.Abs() > 0 {
-		return context.WithTimeout(context.Background(), exec.Timeout.Duration)
-	} else {
-		return timeoutCtx(config, test, step)
-	}
+func timeoutCtx(fallback time.Duration, config *metav1.Duration, test *metav1.Duration, step *metav1.Duration, operation *metav1.Duration) (context.Context, context.CancelFunc) {
+	timeout := timeout(fallback, config, test, step, operation)
+	return context.WithTimeout(context.Background(), timeout)
 }
diff --git a/website/docs/apis/chainsaw.v1alpha1.md b/website/docs/apis/chainsaw.v1alpha1.md
index 84586202..a04a2151 100644
--- a/website/docs/apis/chainsaw.v1alpha1.md
+++ b/website/docs/apis/chainsaw.v1alpha1.md
@@ -120,7 +120,7 @@ during the testing process.</p>
 
 | Field | Type | Required | Inline | Description |
 |---|---|---|---|---|
-| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) |  |  | <p>Global timeout configuration. Applies to all tests/test steps if not overridden.</p> |
+| `timeouts` | [`Timeouts`](#chainsaw-kyverno-io-v1alpha1-Timeouts) |  |  | <p>Global timeouts configuration. Applies to all tests/test steps if not overridden.</p> |
 | `testDirs` | `[]string` |  |  | <p>Directories containing test cases to run.</p> |
 | `skipDelete` | `bool` |  |  | <p>If set, do not delete the resources after running the tests (implies SkipClusterDelete).</p> |
 | `failFast` | `bool` |  |  | <p>FailFast determines whether the test should stop upon encountering the first failure.</p> |
@@ -320,7 +320,7 @@ For multiple objects use labels.</p>
 
 | Field | Type | Required | Inline | Description |
 |---|---|---|---|---|
-| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) |  |  | <p>Timeout for the test. Overrides the global timeout set in the Configuration.</p> |
+| `timeouts` | [`Timeouts`](#chainsaw-kyverno-io-v1alpha1-Timeouts) |  |  | <p>Timeouts for the test. Overrides the global timeouts set in the Configuration on a per operation basis.</p> |
 | `skip` | `bool` |  |  | <p>Skip determines whether the test should skipped.</p> |
 | `concurrent` | `bool` |  |  | <p>Concurrent determines whether the test should run concurrently with other tests.</p> |
 | `skipDelete` | `bool` |  |  | <p>SkipDelete determines whether the resources created by the test should be deleted after the test is executed.</p> |
@@ -353,7 +353,7 @@ For multiple objects use labels.</p>
 
 | Field | Type | Required | Inline | Description |
 |---|---|---|---|---|
-| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) |  |  | <p>Timeout for the test step. Overrides the global timeout set in the Configuration and the timeout eventually set in the Test.</p> |
+| `timeouts` | [`Timeouts`](#chainsaw-kyverno-io-v1alpha1-Timeouts) |  |  | <p>Timeouts for the test step. Overrides the global timeouts set in the Configuration and the timeouts eventually set in the Test.</p> |
 | `skipDelete` | `bool` |  |  | <p>SkipDelete determines whether the resources created by the step should be deleted after the test step is executed.</p> |
 | `assert` | [`[]Assert`](#chainsaw-kyverno-io-v1alpha1-Assert) |  |  | <p>Assert represents the assertions to be made for this test step. It checks whether the conditions specified in each assertion hold true.</p> |
 | `apply` | [`[]Apply`](#chainsaw-kyverno-io-v1alpha1-Apply) |  |  | <p>Apply lists the resources that should be applied for this test step. This can include things like configuration settings or any other resources that need to be available during the test.</p> |
@@ -362,4 +362,24 @@ For multiple objects use labels.</p>
 | `exec` | [`[]ExecOperation`](#chainsaw-kyverno-io-v1alpha1-ExecOperation) |  |  | <p>Exec provides a list of commands and/or scripts that should be executed as a part of this test step.</p> |
 | `onFailure` | [`[]OnFailure`](#chainsaw-kyverno-io-v1alpha1-OnFailure) |  |  | <p>OnFailure defines actions to be executed in case of step failure.</p> |
 
+## `Timeouts`     {#chainsaw-kyverno-io-v1alpha1-Timeouts}
+
+**Appears in:**
+    
+- [ConfigurationSpec](#chainsaw-kyverno-io-v1alpha1-ConfigurationSpec)
+- [TestSpec](#chainsaw-kyverno-io-v1alpha1-TestSpec)
+- [TestStepSpec](#chainsaw-kyverno-io-v1alpha1-TestStepSpec)
+
+<p>Timeouts contains timeouts per operation.</p>
+
+
+| Field | Type | Required | Inline | Description |
+|---|---|---|---|---|
+| `apply` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | :white_check_mark: |  | <p>Apply defines the timeout for the apply operation</p> |
+| `assert` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | :white_check_mark: |  | <p>Assert defines the timeout for the assert operation</p> |
+| `error` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | :white_check_mark: |  | <p>Error defines the timeout for the error operation</p> |
+| `delete` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | :white_check_mark: |  | <p>Delete defines the timeout for the delete operation</p> |
+| `cleanup` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | :white_check_mark: |  | <p>Cleanup defines the timeout for the cleanup operation</p> |
+| `exec` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | :white_check_mark: |  | <p>Exec defines the timeout for exec operations</p> |
+
   
\ No newline at end of file
diff --git a/website/docs/overrides/home.html b/website/docs/overrides/home.html
index 76df70cd..30953e02 100644
--- a/website/docs/overrides/home.html
+++ b/website/docs/overrides/home.html
@@ -103,7 +103,7 @@
 	<div class="md-grid md-typeset">
 		<div class="tx-hero">
 			<div class="tx-hero__image">
-				<img src="static/Kyverno_320x320.png" alt="" draggable="false" style="border-radius: 10%; border: 5px solid #555;">
+				<img src="static/kyverno-chainsaw-logo.png" alt="" draggable="false" style="border-radius: 10%; border: 5px solid #555;">
 			</div>
 			<div class="tx-hero__content">
 				<h1>Kyverno Chainsaw</h1>


Test patch:
diff --git a/.crds/chainsaw.kyverno.io_tests.yaml b/.crds/chainsaw.kyverno.io_tests.yaml
index ff5e8d09..53c892b1 100644
--- a/.crds/chainsaw.kyverno.io_tests.yaml
+++ b/.crds/chainsaw.kyverno.io_tests.yaml
@@ -308,20 +308,63 @@ spec:
                             created by the step should be deleted after the test step
                             is executed.
                           type: boolean
-                        timeout:
-                          description: Timeout for the test step. Overrides the global
-                            timeout set in the Configuration and the timeout eventually
+                        timeouts:
+                          description: Timeouts for the test step. Overrides the global
+                            timeouts set in the Configuration and the timeouts eventually
                             set in the Test.
-                          type: string
+                          properties:
+                            apply:
+                              description: Apply defines the timeout for the apply
+                                operation
+                              type: string
+                            assert:
+                              description: Assert defines the timeout for the assert
+                                operation
+                              type: string
+                            cleanup:
+                              description: Cleanup defines the timeout for the cleanup
+                                operation
+                              type: string
+                            delete:
+                              description: Delete defines the timeout for the delete
+                                operation
+                              type: string
+                            error:
+                              description: Error defines the timeout for the error
+                                operation
+                              type: string
+                            exec:
+                              description: Exec defines the timeout for exec operations
+                              type: string
+                          type: object
                       type: object
                   required:
                   - spec
                   type: object
                 type: array
-              timeout:
-                description: Timeout for the test. Overrides the global timeout set
-                  in the Configuration.
-                type: string
+              timeouts:
+                description: Timeouts for the test. Overrides the global timeouts
+                  set in the Configuration on a per operation basis.
+                properties:
+                  apply:
+                    description: Apply defines the timeout for the apply operation
+                    type: string
+                  assert:
+                    description: Assert defines the timeout for the assert operation
+                    type: string
+                  cleanup:
+                    description: Cleanup defines the timeout for the cleanup operation
+                    type: string
+                  delete:
+                    description: Delete defines the timeout for the delete operation
+                    type: string
+                  error:
+                    description: Error defines the timeout for the error operation
+                    type: string
+                  exec:
+                    description: Exec defines the timeout for exec operations
+                    type: string
+                type: object
             required:
             - steps
             type: object
diff --git a/.crds/chainsaw.kyverno.io_teststeps.yaml b/.crds/chainsaw.kyverno.io_teststeps.yaml
index 0a46481f..4115aee1 100644
--- a/.crds/chainsaw.kyverno.io_teststeps.yaml
+++ b/.crds/chainsaw.kyverno.io_teststeps.yaml
@@ -269,10 +269,30 @@ spec:
                 description: SkipDelete determines whether the resources created by
                   the step should be deleted after the test step is executed.
                 type: boolean
-              timeout:
-                description: Timeout for the test step. Overrides the global timeout
-                  set in the Configuration and the timeout eventually set in the Test.
-                type: string
+              timeouts:
+                description: Timeouts for the test step. Overrides the global timeouts
+                  set in the Configuration and the timeouts eventually set in the
+                  Test.
+                properties:
+                  apply:
+                    description: Apply defines the timeout for the apply operation
+                    type: string
+                  assert:
+                    description: Assert defines the timeout for the assert operation
+                    type: string
+                  cleanup:
+                    description: Cleanup defines the timeout for the cleanup operation
+                    type: string
+                  delete:
+                    description: Delete defines the timeout for the delete operation
+                    type: string
+                  error:
+                    description: Error defines the timeout for the error operation
+                    type: string
+                  exec:
+                    description: Exec defines the timeout for exec operations
+                    type: string
+                type: object
             type: object
         required:
         - spec
diff --git a/pkg/apis/v1alpha1/test_spec.go b/pkg/apis/v1alpha1/test_spec.go
index 11b7e41f..c6c39bc7 100644
--- a/pkg/apis/v1alpha1/test_spec.go
+++ b/pkg/apis/v1alpha1/test_spec.go
@@ -1,14 +1,10 @@
 package v1alpha1
 
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
 // TestSpec contains the test spec.
 type TestSpec struct {
-	// Timeout for the test. Overrides the global timeout set in the Configuration.
+	// Timeouts for the test. Overrides the global timeouts set in the Configuration on a per operation basis.
 	// +optional
-	Timeout *metav1.Duration `json:"timeout,omitempty"`
+	Timeouts Timeouts `json:"timeouts,omitempty"`
 
 	// Skip determines whether the test should skipped.
 	// +optional
diff --git a/pkg/apis/v1alpha1/test_step_spec.go b/pkg/apis/v1alpha1/test_step_spec.go
index 97fc0767..a14e7591 100644
--- a/pkg/apis/v1alpha1/test_step_spec.go
+++ b/pkg/apis/v1alpha1/test_step_spec.go
@@ -1,14 +1,10 @@
 package v1alpha1
 
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
 // TestStepSpec defines the desired state and behavior for each test step.
 type TestStepSpec struct {
-	// Timeout for the test step. Overrides the global timeout set in the Configuration and the timeout eventually set in the Test.
+	// Timeouts for the test step. Overrides the global timeouts set in the Configuration and the timeouts eventually set in the Test.
 	// +optional
-	Timeout *metav1.Duration `json:"timeout,omitempty"`
+	Timeouts Timeouts `json:"timeouts,omitempty"`
 
 	// SkipDelete determines whether the resources created by the step should be deleted after the test step is executed.
 	// +optional
diff --git a/pkg/commands/test/command.go b/pkg/commands/test/command.go
index db9d00f7..4b9d9d9a 100644
--- a/pkg/commands/test/command.go
+++ b/pkg/commands/test/command.go
@@ -6,7 +6,6 @@ import (
 	"io/fs"
 	"os"
 	"path/filepath"
-	"time"
 
 	"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
 	"github.com/kyverno/chainsaw/pkg/config"
@@ -24,7 +23,12 @@ import (
 
 type options struct {
 	config              string
-	timeout             metav1.Duration
+	applyTimeout        metav1.Duration
+	assertTimeout       metav1.Duration
+	errorTimeout        metav1.Duration
+	deleteTimeout       metav1.Duration
+	cleanupTimeout      metav1.Duration
+	execTimeout         metav1.Duration
 	testDirs            []string
 	skipDelete          bool
 	failFast            bool
@@ -81,8 +85,23 @@ func Command() *cobra.Command {
 			}
 			// flags take precedence over configuration file
 			flags := cmd.Flags()
-			if flagutils.IsSet(flags, "timeout") {
-				configuration.Spec.Timeout = &options.timeout
+			if flagutils.IsSet(flags, "apply-timeout") {
+				configuration.Spec.Timeouts.Apply = &options.applyTimeout
+			}
+			if flagutils.IsSet(flags, "assert-timeout") {
+				configuration.Spec.Timeouts.Assert = &options.assertTimeout
+			}
+			if flagutils.IsSet(flags, "error-timeout") {
+				configuration.Spec.Timeouts.Error = &options.errorTimeout
+			}
+			if flagutils.IsSet(flags, "delete-timeout") {
+				configuration.Spec.Timeouts.Delete = &options.deleteTimeout
+			}
+			if flagutils.IsSet(flags, "cleanup-timeout") {
+				configuration.Spec.Timeouts.Cleanup = &options.cleanupTimeout
+			}
+			if flagutils.IsSet(flags, "exec-timeout") {
+				configuration.Spec.Timeouts.Exec = &options.execTimeout
 			}
 			if flagutils.IsSet(flags, "test-dir") {
 				configuration.Spec.TestDirs = options.testDirs
@@ -117,7 +136,6 @@ func Command() *cobra.Command {
 			if flagutils.IsSet(flags, "exclude-test-regex") {
 				configuration.Spec.ExcludeTestRegex = options.excludeTestRegex
 			}
-			fmt.Fprintf(out, "- Timeout %v\n", configuration.Spec.Timeout.Duration)
 			fmt.Fprintf(out, "- TestDirs %v\n", configuration.Spec.TestDirs)
 			fmt.Fprintf(out, "- SkipDelete %v\n", configuration.Spec.SkipDelete)
 			fmt.Fprintf(out, "- FailFast %v\n", configuration.Spec.FailFast)
@@ -133,6 +151,24 @@ func Command() *cobra.Command {
 			if configuration.Spec.RepeatCount != nil {
 				fmt.Fprintf(out, "- RepeatCount %v\n", *configuration.Spec.RepeatCount)
 			}
+			if configuration.Spec.Timeouts.Apply != nil {
+				fmt.Fprintf(out, "- ApplyTimeout %v\n", configuration.Spec.Timeouts.Apply.Duration)
+			}
+			if configuration.Spec.Timeouts.Assert != nil {
+				fmt.Fprintf(out, "- AssertTimeout %v\n", configuration.Spec.Timeouts.Assert.Duration)
+			}
+			if configuration.Spec.Timeouts.Error != nil {
+				fmt.Fprintf(out, "- ErrorTimeout %v\n", configuration.Spec.Timeouts.Error.Duration)
+			}
+			if configuration.Spec.Timeouts.Delete != nil {
+				fmt.Fprintf(out, "- DeleteTimeout %v\n", configuration.Spec.Timeouts.Delete.Duration)
+			}
+			if configuration.Spec.Timeouts.Cleanup != nil {
+				fmt.Fprintf(out, "- CleanupTimeout %v\n", configuration.Spec.Timeouts.Cleanup.Duration)
+			}
+			if configuration.Spec.Timeouts.Exec != nil {
+				fmt.Fprintf(out, "- ExecTimeout %v\n", configuration.Spec.Timeouts.Exec.Duration)
+			}
 			// loading tests
 			fmt.Fprintln(out, "Loading tests...")
 			tests, err := discovery.DiscoverTests("chainsaw-test.yaml", configuration.Spec.TestDirs...)
@@ -166,7 +202,12 @@ func Command() *cobra.Command {
 			return err
 		},
 	}
-	cmd.Flags().DurationVar(&options.timeout.Duration, "timeout", 30*time.Second, "The timeout to use as default for configuration.")
+	cmd.Flags().DurationVar(&options.applyTimeout.Duration, "apply-timeout", 0, "The apply timeout to use as default for configuration.")
+	cmd.Flags().DurationVar(&options.assertTimeout.Duration, "assert-timeout", 0, "The assert timeout to use as default for configuration.")
+	cmd.Flags().DurationVar(&options.errorTimeout.Duration, "error-timeout", 0, "The error timeout to use as default for configuration.")
+	cmd.Flags().DurationVar(&options.deleteTimeout.Duration, "delete-timeout", 0, "The delete timeout to use as default for configuration.")
+	cmd.Flags().DurationVar(&options.cleanupTimeout.Duration, "cleanup-timeout", 0, "The cleanup timeout to use as default for configuration.")
+	cmd.Flags().DurationVar(&options.execTimeout.Duration, "exec-timeout", 0, "The exec timeout to use as default for configuration.")
 	cmd.Flags().StringVar(&options.config, "config", "", "Chainsaw configuration file.")
 	cmd.Flags().StringArrayVar(&options.testDirs, "test-dir", []string{}, "Directories containing test cases to run.")
 	cmd.Flags().BoolVar(&options.skipDelete, "skip-delete", false, "If set, do not delete the resources after running the tests.")
diff --git a/pkg/commands/test/command_test.go b/pkg/commands/test/command_test.go
index f295fc9a..8970a0f6 100644
--- a/pkg/commands/test/command_test.go
+++ b/pkg/commands/test/command_test.go
@@ -26,9 +26,9 @@ func TestChainsawCommand(t *testing.T) {
 			out:     filepath.Join(basePath, "default.txt"),
 		},
 		{
-			name: "with timeout",
+			name: "with apply timeout",
 			args: []string{
-				"--timeout",
+				"--apply-timeout",
 				"10s",
 			},
 			wantErr: false,
@@ -80,7 +80,7 @@ func TestChainsawCommand(t *testing.T) {
 			out:     filepath.Join(basePath, "with_regex.txt"),
 		},
 		{
-			name: "valid config",
+			name: "empty config",
 			args: []string{
 				"--config",
 				filepath.Join(basePath, "config/empty_config.yaml"),
@@ -119,8 +119,6 @@ func TestChainsawCommand(t *testing.T) {
 			args: []string{
 				"--config",
 				filepath.Join(basePath, "config/config_all_fields.yaml"),
-				"--timeout",
-				"10s",
 			},
 			wantErr: false,
 			out:     filepath.Join(basePath, "config_all_fields.txt"),
diff --git a/pkg/config/load_test.go b/pkg/config/load_test.go
index e924a3a8..e386cacd 100644
--- a/pkg/config/load_test.go
+++ b/pkg/config/load_test.go
@@ -44,9 +44,6 @@ func TestLoad(t *testing.T) {
 					Name: "default",
 				},
 				Spec: v1alpha1.ConfigurationSpec{
-					Timeout: &metav1.Duration{
-						Duration: 30 * time.Second,
-					},
 					SkipDelete:       false,
 					FailFast:         false,
 					ReportFormat:     "",
@@ -69,8 +66,13 @@ func TestLoad(t *testing.T) {
 					Name: "custom-config",
 				},
 				Spec: v1alpha1.ConfigurationSpec{
-					Timeout: &metav1.Duration{
-						Duration: 45 * time.Second,
+					Timeouts: v1alpha1.Timeouts{
+						Apply:   &metav1.Duration{Duration: 5 * time.Second},
+						Assert:  &metav1.Duration{Duration: 10 * time.Second},
+						Error:   &metav1.Duration{Duration: 10 * time.Second},
+						Delete:  &metav1.Duration{Duration: 5 * time.Second},
+						Cleanup: &metav1.Duration{Duration: 5 * time.Second},
+						Exec:    &metav1.Duration{Duration: 10 * time.Second},
 					},
 					SkipDelete:       true,
 					FailFast:         true,
diff --git a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
index ff5e8d09..53c892b1 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
@@ -308,20 +308,63 @@ spec:
                             created by the step should be deleted after the test step
                             is executed.
                           type: boolean
-                        timeout:
-                          description: Timeout for the test step. Overrides the global
-                            timeout set in the Configuration and the timeout eventually
+                        timeouts:
+                          description: Timeouts for the test step. Overrides the global
+                            timeouts set in the Configuration and the timeouts eventually
                             set in the Test.
-                          type: string
+                          properties:
+                            apply:
+                              description: Apply defines the timeout for the apply
+                                operation
+                              type: string
+                            assert:
+                              description: Assert defines the timeout for the assert
+                                operation
+                              type: string
+                            cleanup:
+                              description: Cleanup defines the timeout for the cleanup
+                                operation
+                              type: string
+                            delete:
+                              description: Delete defines the timeout for the delete
+                                operation
+                              type: string
+                            error:
+                              description: Error defines the timeout for the error
+                                operation
+                              type: string
+                            exec:
+                              description: Exec defines the timeout for exec operations
+                              type: string
+                          type: object
                       type: object
                   required:
                   - spec
                   type: object
                 type: array
-              timeout:
-                description: Timeout for the test. Overrides the global timeout set
-                  in the Configuration.
-                type: string
+              timeouts:
+                description: Timeouts for the test. Overrides the global timeouts
+                  set in the Configuration on a per operation basis.
+                properties:
+                  apply:
+                    description: Apply defines the timeout for the apply operation
+                    type: string
+                  assert:
+                    description: Assert defines the timeout for the assert operation
+                    type: string
+                  cleanup:
+                    description: Cleanup defines the timeout for the cleanup operation
+                    type: string
+                  delete:
+                    description: Delete defines the timeout for the delete operation
+                    type: string
+                  error:
+                    description: Error defines the timeout for the error operation
+                    type: string
+                  exec:
+                    description: Exec defines the timeout for exec operations
+                    type: string
+                type: object
             required:
             - steps
             type: object
diff --git a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
index 0a46481f..4115aee1 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
@@ -269,10 +269,30 @@ spec:
                 description: SkipDelete determines whether the resources created by
                   the step should be deleted after the test step is executed.
                 type: boolean
-              timeout:
-                description: Timeout for the test step. Overrides the global timeout
-                  set in the Configuration and the timeout eventually set in the Test.
-                type: string
+              timeouts:
+                description: Timeouts for the test step. Overrides the global timeouts
+                  set in the Configuration and the timeouts eventually set in the
+                  Test.
+                properties:
+                  apply:
+                    description: Apply defines the timeout for the apply operation
+                    type: string
+                  assert:
+                    description: Assert defines the timeout for the assert operation
+                    type: string
+                  cleanup:
+                    description: Cleanup defines the timeout for the cleanup operation
+                    type: string
+                  delete:
+                    description: Delete defines the timeout for the delete operation
+                    type: string
+                  error:
+                    description: Error defines the timeout for the error operation
+                    type: string
+                  exec:
+                    description: Exec defines the timeout for exec operations
+                    type: string
+                type: object
             type: object
         required:
         - spec
diff --git a/pkg/runner/logging/logger_test.go b/pkg/runner/logging/logger_test.go
new file mode 100644
index 00000000..bb94850c
--- /dev/null
+++ b/pkg/runner/logging/logger_test.go
@@ -0,0 +1,137 @@
+package logging
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/fatih/color"
+	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type testLogger struct {
+	messages []string
+}
+
+func (tl *testLogger) Log(args ...interface{}) {
+	for _, arg := range args {
+		tl.messages = append(tl.messages, fmt.Sprint(arg))
+	}
+}
+
+func (tl *testLogger) Helper() {}
+
+func TestNewLogger(t *testing.T) {
+	fakeClock := &mockClock{time: time.Now()}
+	testName := "testName"
+	stepName := "stepName"
+	logger, ok := NewLogger(t, fakeClock, testName, stepName).(*logger)
+
+	assert.True(t, ok, "Type assertion for *logger failed")
+
+	assert.Equal(t, t, logger.t)
+	assert.Equal(t, fakeClock, logger.clock)
+	assert.Equal(t, testName, logger.test)
+	assert.Equal(t, stepName, logger.step)
+	assert.Nil(t, logger.resource)
+}
+
+func TestLog(t *testing.T) {
+	fakeClock := &mockClock{time: time.Now()}
+	mockT := &testLogger{}
+
+	fakeLogger := NewLogger(mockT, fakeClock, "testName", "stepName").(*logger)
+	col := color.New(color.FgBlue)
+
+	testCases := []struct {
+		name           string
+		resource       ctrlclient.Object
+		operation      string
+		args           []interface{}
+		expectContains []string
+	}{
+		{
+			name:      "Without Resource",
+			resource:  nil,
+			operation: "OPERATION",
+			args:      []interface{}{"arg1", "arg2"},
+			expectContains: []string{
+				"testName", "stepName", "OPERATION", "arg1", "arg2",
+			},
+		},
+		{
+			name: "With Resource",
+			resource: &testResource{
+				name:      "testResource",
+				namespace: "default",
+				gvk:       schema.GroupVersionKind{Group: "testGroup", Version: "v1", Kind: "testKind"},
+			},
+			operation: "OPERATION",
+			args:      []interface{}{"arg1", "arg2"},
+			expectContains: []string{
+				"testName", "stepName", "OPERATION", "default/testResource", "testGroup/v1/testKind", "arg1", "arg2",
+			},
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.resource != nil {
+				fakeLogger = fakeLogger.WithResource(tt.resource).(*logger)
+			}
+
+			fakeLogger.Log(tt.operation, col, tt.args...)
+			for _, exp := range tt.expectContains {
+				found := false
+				for _, msg := range mockT.messages {
+					if strings.Contains(msg, exp) {
+						found = true
+						break
+					}
+				}
+				assert.True(t, found, "Expected to find '%s' in logs, but didn't. Logs: %v", exp, mockT.messages)
+			}
+			mockT.messages = []string{}
+		})
+	}
+}
+
+func TestWithResource(t *testing.T) {
+	testCases := []struct {
+		name      string
+		resource  ctrlclient.Object
+		expectNil bool
+	}{
+		{"Valid Resource", &testResource{name: "testResource"}, false},
+		{"Nil Resource", nil, true},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			fakeClock := &mockClock{time: time.Now()}
+			fakeLogger := logger{
+				t:     t,
+				clock: fakeClock,
+				test:  "testName",
+				step:  "stepName",
+			}
+
+			newLogger := fakeLogger.WithResource(tt.resource).(*logger)
+
+			if tt.expectNil {
+				assert.Nil(t, newLogger.resource, "Expected resource to be nil in the logger")
+			} else {
+				assert.NotNil(t, newLogger.resource, "Expected resource to not be nil in the logger")
+				assert.Equal(t, tt.resource, newLogger.resource, "Expected correct resource to be set in the logger")
+			}
+
+			assert.Equal(t, fakeLogger.t, newLogger.t, "Expected testing.T to remain the same")
+			assert.Equal(t, fakeLogger.clock, newLogger.clock, "Expected clock to remain the same")
+			assert.Equal(t, fakeLogger.test, newLogger.test, "Expected test name to remain the same")
+			assert.Equal(t, fakeLogger.step, newLogger.step, "Expected step name to remain the same")
+		})
+	}
+}
diff --git a/pkg/runner/timeout_test.go b/pkg/runner/timeout_test.go
index 2e6ef825..9b69549b 100644
--- a/pkg/runner/timeout_test.go
+++ b/pkg/runner/timeout_test.go
@@ -4,247 +4,130 @@ import (
 	"testing"
 	"time"
 
-	"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
 	"github.com/stretchr/testify/assert"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/utils/ptr"
 )
 
 func Test_timeout(t *testing.T) {
+	config := metav1.Duration{Duration: 1 * time.Minute}
+	test := metav1.Duration{Duration: 2 * time.Minute}
+	step := metav1.Duration{Duration: 3 * time.Minute}
+	operation := metav1.Duration{Duration: 4 * time.Minute}
+	fallback := 10 * time.Second
 	tests := []struct {
-		name   string
-		config v1alpha1.ConfigurationSpec
-		test   v1alpha1.TestSpec
-		step   v1alpha1.TestStepSpec
-		want   *time.Duration
+		name      string
+		fallback  time.Duration
+		config    *metav1.Duration
+		test      *metav1.Duration
+		step      *metav1.Duration
+		operation *metav1.Duration
+		want      time.Duration
 	}{{
-		name: "none",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		want: nil,
+		name:      "none",
+		fallback:  fallback,
+		config:    nil,
+		test:      nil,
+		step:      nil,
+		operation: nil,
+		want:      fallback,
 	}, {
-		name: "from config",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: &metav1.Duration{Duration: 1 * time.Minute},
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		want: ptr.To(1 * time.Minute),
+		name:      "from config",
+		fallback:  fallback,
+		config:    &config,
+		test:      nil,
+		step:      nil,
+		operation: nil,
+		want:      config.Duration,
 	}, {
-		name: "from test",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: &metav1.Duration{Duration: 1 * time.Minute},
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: &metav1.Duration{Duration: 2 * time.Minute},
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		want: ptr.To(2 * time.Minute),
+		name:      "from test",
+		fallback:  fallback,
+		config:    &config,
+		test:      &test,
+		step:      nil,
+		operation: nil,
+		want:      test.Duration,
 	}, {
-		name: "from step",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: &metav1.Duration{Duration: 1 * time.Minute},
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: &metav1.Duration{Duration: 2 * time.Minute},
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: &metav1.Duration{Duration: 3 * time.Minute},
-		},
-		want: ptr.To(3 * time.Minute),
+		name:      "from step",
+		fallback:  fallback,
+		config:    &config,
+		test:      &test,
+		step:      &step,
+		operation: nil,
+		want:      step.Duration,
+	}, {
+		name:      "from operation",
+		fallback:  fallback,
+		config:    &config,
+		test:      &test,
+		step:      &step,
+		operation: &operation,
+		want:      operation.Duration,
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := timeout(tt.config, tt.test, tt.step)
+			got := timeout(tt.fallback, tt.config, tt.test, tt.step, tt.operation)
 			assert.Equal(t, tt.want, got)
 		})
 	}
 }
 
-func Test_cancelNoOp(t *testing.T) {
-	tests := []struct {
-		name string
-	}{{}}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			cancelNoOp()
-		})
-	}
-}
-
 func Test_timeoutCtx(t *testing.T) {
+	config := metav1.Duration{Duration: 1 * time.Minute}
+	test := metav1.Duration{Duration: 2 * time.Minute}
+	step := metav1.Duration{Duration: 3 * time.Minute}
+	operation := metav1.Duration{Duration: 4 * time.Minute}
+	fallback := 10 * time.Second
 	tests := []struct {
-		name   string
-		config v1alpha1.ConfigurationSpec
-		test   v1alpha1.TestSpec
-		step   v1alpha1.TestStepSpec
-		want   bool
-	}{{
-		name: "none",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		want: false,
-	}, {
-		name: "from config",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: &metav1.Duration{Duration: 1 * time.Minute},
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		want: true,
-	}, {
-		name: "from test",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: &metav1.Duration{Duration: 2 * time.Minute},
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		want: true,
-	}, {
-		name: "from step",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: &metav1.Duration{Duration: 3 * time.Minute},
-		},
-		want: true,
-	}}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, cancel := timeoutCtx(tt.config, tt.test, tt.step)
-			defer cancel()
-			assert.NotNil(t, got)
-			assert.NotNil(t, cancel)
-			_, ok := got.Deadline()
-			assert.Equal(t, tt.want, ok)
-		})
-	}
-}
-
-func Test_timeoutExecCtx(t *testing.T) {
-	tests := []struct {
-		name   string
-		config v1alpha1.ConfigurationSpec
-		test   v1alpha1.TestSpec
-		step   v1alpha1.TestStepSpec
-		exec   v1alpha1.Exec
-		want   bool
+		name      string
+		fallback  time.Duration
+		config    *metav1.Duration
+		test      *metav1.Duration
+		step      *metav1.Duration
+		operation *metav1.Duration
 	}{{
-		name: "none",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		exec: v1alpha1.Exec{
-			Timeout: nil,
-		},
-		want: false,
+		name:      "none",
+		fallback:  fallback,
+		config:    nil,
+		test:      nil,
+		step:      nil,
+		operation: nil,
 	}, {
-		name: "from config",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: &metav1.Duration{Duration: 1 * time.Minute},
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		exec: v1alpha1.Exec{
-			Timeout: nil,
-		},
-		want: true,
+		name:      "from config",
+		fallback:  fallback,
+		config:    &config,
+		test:      nil,
+		step:      nil,
+		operation: nil,
 	}, {
-		name: "from test",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: &metav1.Duration{Duration: 2 * time.Minute},
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		exec: v1alpha1.Exec{
-			Timeout: nil,
-		},
-		want: true,
+		name:      "from test",
+		fallback:  fallback,
+		config:    &config,
+		test:      &test,
+		step:      nil,
+		operation: nil,
 	}, {
-		name: "from step",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: &metav1.Duration{Duration: 3 * time.Minute},
-		},
-		exec: v1alpha1.Exec{
-			Timeout: nil,
-		},
-		want: true,
+		name:      "from step",
+		fallback:  fallback,
+		config:    &config,
+		test:      &test,
+		step:      &step,
+		operation: nil,
 	}, {
-		name: "from exec",
-		config: v1alpha1.ConfigurationSpec{
-			Timeout: nil,
-		},
-		test: v1alpha1.TestSpec{
-			Timeout: nil,
-		},
-		step: v1alpha1.TestStepSpec{
-			Timeout: nil,
-		},
-		exec: v1alpha1.Exec{
-			Timeout: &metav1.Duration{Duration: 4 * time.Minute},
-		},
-		want: true,
+		name:      "from operation",
+		fallback:  fallback,
+		config:    &config,
+		test:      &test,
+		step:      &step,
+		operation: &operation,
 	}}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, cancel := timeoutExecCtx(tt.exec, tt.config, tt.test, tt.step)
+			got, cancel := timeoutCtx(tt.fallback, tt.config, tt.test, tt.step, tt.operation)
 			defer cancel()
 			assert.NotNil(t, got)
 			assert.NotNil(t, cancel)
 			_, ok := got.Deadline()
-			assert.Equal(t, tt.want, ok)
+			assert.Equal(t, true, ok)
 		})
 	}
 }
diff --git a/testdata/commands/test/config/config_all_fields.yaml b/testdata/commands/test/config/config_all_fields.yaml
index 23541b57..22db0d93 100644
--- a/testdata/commands/test/config/config_all_fields.yaml
+++ b/testdata/commands/test/config/config_all_fields.yaml
@@ -4,16 +4,22 @@ metadata:
   name: valid
   namespace: kyverno
 spec:
-  timeout: "60s"
+  timeouts:
+    apply: 5s
+    assert: 10s
+    error: 10s
+    delete: 5s
+    cleanup: 5s
+    exec: 10s
   testDirs:
-    - "/path/to/testdir1"
-    - "/path/to/testdir2"
+    - /path/to/testdir1
+    - /path/to/testdir2
   skipDelete: true
   failFast: true
   parallel: 5
-  reportFormat: "JSON"
-  reportName: "custom-chainsaw-report"
-  namespace: "test-namespace"
+  reportFormat: JSON
+  reportName: custom-chainsaw-report
+  namespace: test-namespace
   fullName: true
-  includeTestRegex: "^include-.*"
-  excludeTestRegex: "^exclude-.*"
+  includeTestRegex: ^include-.*
+  excludeTestRegex: ^exclude-.*
diff --git a/testdata/commands/test/config_all_fields.txt b/testdata/commands/test/config_all_fields.txt
index b0ffbc65..5ded8bd9 100644
--- a/testdata/commands/test/config_all_fields.txt
+++ b/testdata/commands/test/config_all_fields.txt
@@ -1,5 +1,4 @@
 Loading config (../../../testdata/commands/test/config/config_all_fields.yaml)...
-- Timeout 10s
 - TestDirs [/path/to/testdir1 /path/to/testdir2]
 - SkipDelete true
 - FailFast true
@@ -10,6 +9,12 @@ Loading config (../../../testdata/commands/test/config/config_all_fields.yaml)..
 - IncludeTestRegex '^include-.*'
 - ExcludeTestRegex '^exclude-.*'
 - Parallel 5
+- ApplyTimeout 5s
+- AssertTimeout 10s
+- ErrorTimeout 10s
+- DeleteTimeout 5s
+- CleanupTimeout 5s
+- ExecTimeout 10s
 Loading tests...
 Running tests...
 Tests Summary...
diff --git a/testdata/commands/test/default.txt b/testdata/commands/test/default.txt
index b4d40a02..30b833ba 100644
--- a/testdata/commands/test/default.txt
+++ b/testdata/commands/test/default.txt
@@ -1,5 +1,4 @@
 Loading default configuration...
-- Timeout 30s
 - TestDirs []
 - SkipDelete false
 - FailFast false
diff --git a/testdata/commands/test/with_regex.txt b/testdata/commands/test/with_regex.txt
index 74992370..c1e0bbaa 100644
--- a/testdata/commands/test/with_regex.txt
+++ b/testdata/commands/test/with_regex.txt
@@ -1,5 +1,4 @@
 Loading default configuration...
-- Timeout 30s
 - TestDirs []
 - SkipDelete false
 - FailFast false
diff --git a/testdata/commands/test/with_repeat_count.txt b/testdata/commands/test/with_repeat_count.txt
index e4456e8f..2d083149 100644
--- a/testdata/commands/test/with_repeat_count.txt
+++ b/testdata/commands/test/with_repeat_count.txt
@@ -1,5 +1,4 @@
 Loading default configuration...
-- Timeout 30s
 - TestDirs []
 - SkipDelete false
 - FailFast false
diff --git a/testdata/commands/test/with_test_dirs.txt b/testdata/commands/test/with_test_dirs.txt
index df915a1b..05412f6f 100644
--- a/testdata/commands/test/with_test_dirs.txt
+++ b/testdata/commands/test/with_test_dirs.txt
@@ -1,5 +1,4 @@
 Loading default configuration...
-- Timeout 30s
 - TestDirs [dir1,dir2,dir3]
 - SkipDelete false
 - FailFast false
diff --git a/testdata/commands/test/with_timeout.txt b/testdata/commands/test/with_timeout.txt
index c89d9e1d..e424b0eb 100644
--- a/testdata/commands/test/with_timeout.txt
+++ b/testdata/commands/test/with_timeout.txt
@@ -1,5 +1,4 @@
 Loading default configuration...
-- Timeout 10s
 - TestDirs []
 - SkipDelete false
 - FailFast false
@@ -9,6 +8,7 @@ Loading default configuration...
 - FullName false
 - IncludeTestRegex ''
 - ExcludeTestRegex ''
+- ApplyTimeout 10s
 Loading tests...
 Running tests...
 Tests Summary...
diff --git a/testdata/config/custom-config.yaml b/testdata/config/custom-config.yaml
index 7ef95ee3..20f74093 100644
--- a/testdata/config/custom-config.yaml
+++ b/testdata/config/custom-config.yaml
@@ -3,7 +3,13 @@ kind: Configuration
 metadata:
   name: custom-config
 spec:
-  timeout: 45s
+  timeouts:
+    apply: 5s
+    assert: 10s
+    error: 10s
+    delete: 5s
+    cleanup: 5s
+    exec: 10s
   skipDelete: true
   failFast: true
   parallel: 4
diff --git a/testdata/config/multiple.yaml b/testdata/config/multiple.yaml
index c83fd246..891c4dc0 100644
--- a/testdata/config/multiple.yaml
+++ b/testdata/config/multiple.yaml
@@ -9,4 +9,5 @@ kind: Configuration
 metadata:
   name: timeout-1m
 spec:
-  timeout: 1m
+  timeouts:
+    assert: 1m
diff --git a/website/docs/commands/chainsaw_test.md b/website/docs/commands/chainsaw_test.md
index 4cfa9813..e3889bc4 100644
--- a/website/docs/commands/chainsaw_test.md
+++ b/website/docs/commands/chainsaw_test.md
@@ -9,8 +9,14 @@ chainsaw test [flags]... [test directories]...
 ### Options
 
 ```
+      --apply-timeout duration              The apply timeout to use as default for configuration.
+      --assert-timeout duration             The assert timeout to use as default for configuration.
+      --cleanup-timeout duration            The cleanup timeout to use as default for configuration.
       --config string                       Chainsaw configuration file.
+      --delete-timeout duration             The delete timeout to use as default for configuration.
+      --error-timeout duration              The error timeout to use as default for configuration.
       --exclude-test-regex string           Regular expression to exclude tests.
+      --exec-timeout duration               The exec timeout to use as default for configuration.
       --full-name                           Use full test case folder path instead of folder name.
   -h, --help                                help for test
       --include-test-regex string           Regular expression to include tests.
@@ -42,7 +48,6 @@ chainsaw test [flags]... [test directories]...
       --skip-delete                         If set, do not delete the resources after running the tests.
       --stop-on-first-failure               Stop the test upon encountering the first failure.
       --test-dir stringArray                Directories containing test cases to run.
-      --timeout duration                    The timeout to use as default for configuration. (default 30s)
 ```
 
 ### SEE ALSO