Repository: grafana/k6-operator

Issue:
Add resources field to initContainer in TestRun CRD
### Feature Description

Add the resource field to the initContainer within the TestRun Custom Resource Definition (CRD). This would allow users to explicitly configure CPU and memory requests and limits, ensuring that the initContainer is scheduled and runs stably.

### Suggested Solution (optional)

```yaml
apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: my-test
spec:
  # ... other fields
  runner:
    initContainers:
      - name: example
        image: "busybox"
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
  # ... other fields
```

### Already existing or connected issues / PRs (optional)

https://github.com/grafana/k6-operator/pull/652

Hints from issue comments:
@yorugac: Hi @gcaldasnu, thanks for the issue! I'm a bit confused here: we don't have `.spec.sidecars` in the `TestRun` CRD. Init containers are set via pod config like `.spec.runner.initContainers`. But looking at your [PR](), it seems that's what you meant? I.e. just extending the existing init containers.

Also, could you please describe your use case? For example, could you share what your init container is doing? I think we didn't have this request before so I'd assume usually, folk are not doing anything too heavy with the init containers 🙂  @gcaldasnu: @yorugac You are right! Sorry, I corrected my example.
To answer your question, my scenario is as follows: I'm running a load test that uses an initContainer, which is responsible for preparing authentication for the main container. However, this initialization step is a cumbersome and time-consuming process, as it involves setting up a complex authentication environment.@rahul-agrawal-zepto: We also have a similar use case for explicitly defining resources for our init containers. As no API calls can be made before the setup phase (for the shared array data), we are fetching some data in the init context. Also, as we define limit ranges and resource quotas for other pods, we need an explicit way to define resources so that it does not default to the limit ranges' values.
It's not necessarily a heavy operation, but the inability to do so restricts us from having this in a shared namespace. A workaround can be putting on different namespaces with different limit range values, but if we have a set of tests defined, we need granular control over the resources used, and it wouldn't be optimal to have a separate namespace for each of the defined configurations.@yorugac: Closed in https://github.com/grafana/k6-operator/pull/652

Solution patch:
diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml
index 839c158..8dbb7ae 100644
--- a/.github/workflows/push.yaml
+++ b/.github/workflows/push.yaml
@@ -116,7 +116,7 @@ jobs:
           git diff --cached | tee diff.file
           echo "BUNDLE_DIFF=$(wc -c < diff.file)" >> "$GITHUB_OUTPUT"
       - name: "Make PR"
-        if: ${{ steps.branch.BUNDLE_DIFF }} > 0
+        if: steps.branch.BUNDLE_DIFF > 0
         run: |
           git config user.name "github-actions[bot]"
           git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 10d98b8..c98be21 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -60,6 +60,7 @@ func (in *InitContainer) DeepCopyInto(out *InitContainer) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	in.Resources.DeepCopyInto(&out.Resources)
 	if in.RestartPolicy != nil {
 		in, out := &in.RestartPolicy, &out.RestartPolicy
 		*out = new(v1.ContainerRestartPolicy)
diff --git a/config/crd/bases/k6.io_testruns.yaml b/config/crd/bases/k6.io_testruns.yaml
index 0b02b3a..5143b36 100644
--- a/config/crd/bases/k6.io_testruns.yaml
+++ b/config/crd/bases/k6.io_testruns.yaml
@@ -802,6 +802,39 @@ spec:
                           type: string
                         name:
                           type: string
+                        resources:
+                          properties:
+                            claims:
+                              items:
+                                properties:
+                                  name:
+                                    type: string
+                                  request:
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                              type: array
+                              x-kubernetes-list-map-keys:
+                              - name
+                              x-kubernetes-list-type: map
+                            limits:
+                              additionalProperties:
+                                anyOf:
+                                - type: integer
+                                - type: string
+                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                x-kubernetes-int-or-string: true
+                              type: object
+                            requests:
+                              additionalProperties:
+                                anyOf:
+                                - type: integer
+                                - type: string
+                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                x-kubernetes-int-or-string: true
+                              type: object
+                          type: object
                         restartPolicy:
                           type: string
                         volumeMounts:
@@ -926,6 +959,8 @@ spec:
                     additionalProperties:
                       type: string
                     type: object
+                  priorityClassName:
+                    type: string
                   readinessProbe:
                     properties:
                       exec:
@@ -2802,6 +2837,39 @@ spec:
                           type: string
                         name:
                           type: string
+                        resources:
+                          properties:
+                            claims:
+                              items:
+                                properties:
+                                  name:
+                                    type: string
+                                  request:
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                              type: array
+                              x-kubernetes-list-map-keys:
+                              - name
+                              x-kubernetes-list-type: map
+                            limits:
+                              additionalProperties:
+                                anyOf:
+                                - type: integer
+                                - type: string
+                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                x-kubernetes-int-or-string: true
+                              type: object
+                            requests:
+                              additionalProperties:
+                                anyOf:
+                                - type: integer
+                                - type: string
+                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                x-kubernetes-int-or-string: true
+                              type: object
+                          type: object
                         restartPolicy:
                           type: string
                         volumeMounts:
@@ -2926,6 +2994,8 @@ spec:
                     additionalProperties:
                       type: string
                     type: object
+                  priorityClassName:
+                    type: string
                   readinessProbe:
                     properties:
                       exec:
@@ -4823,6 +4893,39 @@ spec:
                           type: string
                         name:
                           type: string
+                        resources:
+                          properties:
+                            claims:
+                              items:
+                                properties:
+                                  name:
+                                    type: string
+                                  request:
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                              type: array
+                              x-kubernetes-list-map-keys:
+                              - name
+                              x-kubernetes-list-type: map
+                            limits:
+                              additionalProperties:
+                                anyOf:
+                                - type: integer
+                                - type: string
+                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                x-kubernetes-int-or-string: true
+                              type: object
+                            requests:
+                              additionalProperties:
+                                anyOf:
+                                - type: integer
+                                - type: string
+                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                                x-kubernetes-int-or-string: true
+                              type: object
+                          type: object
                         restartPolicy:
                           type: string
                         volumeMounts:
@@ -4947,6 +5050,8 @@ spec:
                     additionalProperties:
                       type: string
                     type: object
+                  priorityClassName:
+                    type: string
                   readinessProbe:
                     properties:
                       exec:
diff --git a/docs/crd-generated.md b/docs/crd-generated.md
index d066899..0d681b5 100644
--- a/docs/crd-generated.md
+++ b/docs/crd-generated.md
@@ -814,7 +814,8 @@ It is mounted as a `/test` folder to all k6 Pods.<br/>
         <td><b>file</b></td>
         <td>string</td>
         <td>
-          Name of the file to execute (.js or .tar), stored on the Volume.<br/>
+          Name of the file to execute (.js or .tar), stored on the Volume.
+Can include a path component (e.g., "subdir/script.js").<br/>
         </td>
         <td>false</td>
       </tr><tr>
@@ -932,6 +933,13 @@ alive or ready to receive traffic.<br/>
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b>priorityClassName</b></td>
+        <td>string</td>
+        <td>
+          <br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b><a href="#testrunspecinitializerreadinessprobe">readinessProbe</a></b></td>
         <td>object</td>
@@ -3556,6 +3564,13 @@ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/nam
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b><a href="#testrunspecinitializerinitcontainersindexresources">resources</a></b></td>
+        <td>object</td>
+        <td>
+          ResourceRequirements describes the compute resource requirements.<br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b>restartPolicy</b></td>
         <td>string</td>
@@ -4043,6 +4058,95 @@ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/nam
 </table>
 
 
+### TestRun.spec.initializer.initContainers[index].resources
+<sup><sup>[↩ Parent](#testrunspecinitializerinitcontainersindex)</sup></sup>
+
+
+
+ResourceRequirements describes the compute resource requirements.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#testrunspecinitializerinitcontainersindexresourcesclaimsindex">claims</a></b></td>
+        <td>[]object</td>
+        <td>
+          Claims lists the names of resources, defined in spec.resourceClaims,
+that are used by this container.
+
+This field depends on the
+DynamicResourceAllocation feature gate.
+
+This field is immutable. It can only be set for containers.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>limits</b></td>
+        <td>map[string]int or string</td>
+        <td>
+          Limits describes the maximum amount of compute resources allowed.
+More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>requests</b></td>
+        <td>map[string]int or string</td>
+        <td>
+          Requests describes the minimum amount of compute resources required.
+If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
+otherwise to an implementation-defined value. Requests cannot exceed Limits.
+More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### TestRun.spec.initializer.initContainers[index].resources.claims[index]
+<sup><sup>[↩ Parent](#testrunspecinitializerinitcontainersindexresources)</sup></sup>
+
+
+
+ResourceClaim references one entry in PodSpec.ResourceClaims.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>name</b></td>
+        <td>string</td>
+        <td>
+          Name must match the name of one entry in pod.spec.resourceClaims of
+the Pod where this field is used. It makes that resource available
+inside a container.<br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>request</b></td>
+        <td>string</td>
+        <td>
+          Request is the name chosen for a request in the referenced claim.
+If empty, everything from the claim is made available, otherwise
+only the result of this request.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
 ### TestRun.spec.initializer.initContainers[index].volumeMounts[index]
 <sup><sup>[↩ Parent](#testrunspecinitializerinitcontainersindex)</sup></sup>
 
@@ -9782,6 +9886,13 @@ alive or ready to receive traffic.<br/>
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b>priorityClassName</b></td>
+        <td>string</td>
+        <td>
+          <br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b><a href="#testrunspecrunnerreadinessprobe">readinessProbe</a></b></td>
         <td>object</td>
@@ -12406,6 +12517,13 @@ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/nam
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b><a href="#testrunspecrunnerinitcontainersindexresources">resources</a></b></td>
+        <td>object</td>
+        <td>
+          ResourceRequirements describes the compute resource requirements.<br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b>restartPolicy</b></td>
         <td>string</td>
@@ -12893,6 +13011,95 @@ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/nam
 </table>
 
 
+### TestRun.spec.runner.initContainers[index].resources
+<sup><sup>[↩ Parent](#testrunspecrunnerinitcontainersindex)</sup></sup>
+
+
+
+ResourceRequirements describes the compute resource requirements.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#testrunspecrunnerinitcontainersindexresourcesclaimsindex">claims</a></b></td>
+        <td>[]object</td>
+        <td>
+          Claims lists the names of resources, defined in spec.resourceClaims,
+that are used by this container.
+
+This field depends on the
+DynamicResourceAllocation feature gate.
+
+This field is immutable. It can only be set for containers.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>limits</b></td>
+        <td>map[string]int or string</td>
+        <td>
+          Limits describes the maximum amount of compute resources allowed.
+More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>requests</b></td>
+        <td>map[string]int or string</td>
+        <td>
+          Requests describes the minimum amount of compute resources required.
+If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
+otherwise to an implementation-defined value. Requests cannot exceed Limits.
+More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### TestRun.spec.runner.initContainers[index].resources.claims[index]
+<sup><sup>[↩ Parent](#testrunspecrunnerinitcontainersindexresources)</sup></sup>
+
+
+
+ResourceClaim references one entry in PodSpec.ResourceClaims.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>name</b></td>
+        <td>string</td>
+        <td>
+          Name must match the name of one entry in pod.spec.resourceClaims of
+the Pod where this field is used. It makes that resource available
+inside a container.<br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>request</b></td>
+        <td>string</td>
+        <td>
+          Request is the name chosen for a request in the referenced claim.
+If empty, everything from the claim is made available, otherwise
+only the result of this request.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
 ### TestRun.spec.runner.initContainers[index].volumeMounts[index]
 <sup><sup>[↩ Parent](#testrunspecrunnerinitcontainersindex)</sup></sup>
 
@@ -18654,6 +18861,13 @@ alive or ready to receive traffic.<br/>
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b>priorityClassName</b></td>
+        <td>string</td>
+        <td>
+          <br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b><a href="#testrunspecstarterreadinessprobe">readinessProbe</a></b></td>
         <td>object</td>
@@ -21278,6 +21492,13 @@ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/nam
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b><a href="#testrunspecstarterinitcontainersindexresources">resources</a></b></td>
+        <td>object</td>
+        <td>
+          ResourceRequirements describes the compute resource requirements.<br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b>restartPolicy</b></td>
         <td>string</td>
@@ -21765,6 +21986,95 @@ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/nam
 </table>
 
 
+### TestRun.spec.starter.initContainers[index].resources
+<sup><sup>[↩ Parent](#testrunspecstarterinitcontainersindex)</sup></sup>
+
+
+
+ResourceRequirements describes the compute resource requirements.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#testrunspecstarterinitcontainersindexresourcesclaimsindex">claims</a></b></td>
+        <td>[]object</td>
+        <td>
+          Claims lists the names of resources, defined in spec.resourceClaims,
+that are used by this container.
+
+This field depends on the
+DynamicResourceAllocation feature gate.
+
+This field is immutable. It can only be set for containers.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>limits</b></td>
+        <td>map[string]int or string</td>
+        <td>
+          Limits describes the maximum amount of compute resources allowed.
+More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>requests</b></td>
+        <td>map[string]int or string</td>
+        <td>
+          Requests describes the minimum amount of compute resources required.
+If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
+otherwise to an implementation-defined value. Requests cannot exceed Limits.
+More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### TestRun.spec.starter.initContainers[index].resources.claims[index]
+<sup><sup>[↩ Parent](#testrunspecstarterinitcontainersindexresources)</sup></sup>
+
+
+
+ResourceClaim references one entry in PodSpec.ResourceClaims.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>name</b></td>
+        <td>string</td>
+        <td>
+          Name must match the name of one entry in pod.spec.resourceClaims of
+the Pod where this field is used. It makes that resource available
+inside a container.<br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>request</b></td>
+        <td>string</td>
+        <td>
+          Request is the name chosen for a request in the referenced claim.
+If empty, everything from the claim is made available, otherwise
+only the result of this request.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
 ### TestRun.spec.starter.initContainers[index].volumeMounts[index]
 <sup><sup>[↩ Parent](#testrunspecstarterinitcontainersindex)</sup></sup>
 
diff --git a/e2e/invalid-arguments/manifests/configmap.yaml b/e2e/invalid-arguments/manifests/configmap.yaml
new file mode 100644
index 0000000..414ec00
--- /dev/null
+++ b/e2e/invalid-arguments/manifests/configmap.yaml
@@ -0,0 +1,21 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: test
+  namespace: default
+data:
+  test.js: |
+    import http from 'k6/http';
+    import { check } from 'k6';
+
+    export let options = {
+      vus: 1,
+      duration: '50s'
+    };
+
+    export default function () {
+      const result = http.get('https://quickpizza.grafana.com');
+      check(result, {
+        'http response status code is 200': result.status === 200,
+      });
+    }
diff --git a/e2e/invalid-arguments/manifests/kustomization.yaml b/e2e/invalid-arguments/manifests/kustomization.yaml
new file mode 100644
index 0000000..f4de6f5
--- /dev/null
+++ b/e2e/invalid-arguments/manifests/kustomization.yaml
@@ -0,0 +1,5 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- ../../latest/
+- configmap.yaml
diff --git a/e2e/invalid-arguments/test.js b/e2e/invalid-arguments/test.js
new file mode 100644
index 0000000..a2a4cab
--- /dev/null
+++ b/e2e/invalid-arguments/test.js
@@ -0,0 +1,46 @@
+import { Environment } from 'k6/x/environment';
+import { sleep, fail } from 'k6';
+
+export const options = {
+  setupTimeout: '60s',
+};
+
+const PARENT = "./"
+
+const env = new Environment({
+  name: "invalid-arguments",
+  implementation: "vcluster",
+  initFolder: PARENT + "manifests", // initial folder with everything that will be loaded at init
+})
+
+export function setup() {
+  console.log("init returns", env.init());
+  // it is best to have a bit of delay between creating a CRD and 
+  // a corresponding CR, so as to avoid the "no matches" error
+  sleep(0.5);
+}
+
+// TestRun should enter error stage on invalid .spec.arguments
+export default function () {
+  let err = env.apply(PARENT + "testrun.yaml");
+  console.log("apply testrun returns", err)
+
+  err = env.wait({
+    kind: "TestRun",
+    name: "k6-sample", //tr.name(),
+    namespace: "default",  //tr.namespace(),
+    status_key: "stage",
+    status_value: "error",
+  }, {
+    timeout: "5m",
+    interval: "1m",
+  });
+
+  if (err != null) {
+    fail("wait returns" + err);
+  }
+}
+
+export function teardown() {
+  console.log("delete returns", env.delete());
+}
\ No newline at end of file
diff --git a/e2e/invalid-arguments/testrun.yaml b/e2e/invalid-arguments/testrun.yaml
new file mode 100644
index 0000000..eb1a3c2
--- /dev/null
+++ b/e2e/invalid-arguments/testrun.yaml
@@ -0,0 +1,12 @@
+---
+apiVersion: k6.io/v1alpha1
+kind: TestRun
+metadata:
+  name: k6-sample
+spec:
+  parallelism: 1
+  arguments: this argument-is-invalid
+  script:
+    configMap:
+      name: "test"
+      file: "test.js"
diff --git a/e2e/run-tests.sh b/e2e/run-tests.sh
index 06b7a50..7d72daf 100755
--- a/e2e/run-tests.sh
+++ b/e2e/run-tests.sh
@@ -130,6 +130,7 @@ tests=(
   "init-container-volume"
   "multifile"
   "error-stage"
+  "invalid-arguments"
   "testrun-simultaneous"
   "testrun-watch-namespace"
   "testrun-watch-namespaces"
diff --git a/internal/controller/k6_initialize.go b/internal/controller/k6_initialize.go
index 83f2bf9..b8f6352 100644
--- a/internal/controller/k6_initialize.go
+++ b/internal/controller/k6_initialize.go
@@ -21,7 +21,8 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.TestRun,
 	// initializer is a quick job so check in frequently
 	res = ctrl.Result{RequeueAfter: time.Second * 5}
 
-	cli := types.ParseCLI(k6.GetSpec().Arguments)
+	// validation has already happened, so error here can be ignored
+	cli, _ := types.ParseCLI(k6.GetSpec().Arguments)
 
 	var initializer *batchv1.Job
 	if initializer, err = jobs.NewInitializerJob(k6, cli.ArchiveArgs); err != nil {
@@ -50,7 +51,8 @@ func RunValidations(ctx context.Context, log logr.Logger, k6 *v1alpha1.TestRun,
 	// initializer is a quick job so check in frequently
 	res = ctrl.Result{RequeueAfter: time.Second * 5}
 
-	cli := types.ParseCLI(k6.GetSpec().Arguments)
+	// validation has already happened, so error here can be ignored
+	cli, _ := types.ParseCLI(k6.GetSpec().Arguments)
 
 	inspectOutput, inspectReady, err := inspectTestRun(ctx, log, k6, r.Client)
 	if err != nil {
@@ -63,7 +65,7 @@ func RunValidations(ctx context.Context, log logr.Logger, k6 *v1alpha1.TestRun,
 				WithAbort()
 			cloud.SendTestRunEvents(r.k6CloudClient, k6.TestRunID(), log, events)
 		} else {
-			// if there is any error, we have to reflect it on the K6 manifest
+			// if there is any error, we have to reflect it on the TestRun manifest
 			k6.GetStatus().Stage = "error"
 			if _, err := r.UpdateStatus(ctx, k6, log); err != nil {
 				return ctrl.Result{}, ready, err
diff --git a/pkg/cloud/cloud_output.go b/pkg/cloud/cloud_output.go
index a8d1e02..94223e0 100644
--- a/pkg/cloud/cloud_output.go
+++ b/pkg/cloud/cloud_output.go
@@ -24,8 +24,10 @@ type TestRun struct {
 	Instances         int32               `json:"instances"`
 }
 
-func NewClient(log logr.Logger, token, host string) *cloudapi.Client {
-	logger := &logrus.Logger{
+// logger is currently unused, because of logrus dependency in cloudapi.
+// This will have a re-visit during or after https://github.com/grafana/k6-operator/issues/571
+func NewClient(logger logr.Logger, token, host string) *cloudapi.Client {
+	l := &logrus.Logger{
 		Out:       os.Stdout,
 		Formatter: new(logrus.TextFormatter),
 		Hooks:     make(logrus.LevelHooks),
@@ -38,15 +40,13 @@ func NewClient(log logr.Logger, token, host string) *cloudapi.Client {
 		host = cloudConfig.Host.String
 	}
 
+	logrusLogger := l.WithFields(logrus.Fields{"k6_cloud_host": host})
+
 	// TODO: how to get the version now?
-	return cloudapi.NewClient(logger, token, host, "1.2.3", time.Duration(time.Minute))
+	return cloudapi.NewClient(logrusLogger, token, host, "1.2.3", time.Duration(time.Minute))
 }
 
 func CreateTestRun(opts InspectOutput, instances int32, host, token string, log logr.Logger) (*cloudapi.CreateTestRunResponse, error) {
-	if client == nil {
-		client = NewClient(log, token, host)
-	}
-
 	cloudConfig := cloudapi.NewConfig()
 
 	if opts.ProjectID() > 0 {
diff --git a/pkg/resources/containers/curl_start.go b/pkg/resources/containers/curl_start.go
index 9868041..fb2a2f6 100644
--- a/pkg/resources/containers/curl_start.go
+++ b/pkg/resources/containers/curl_start.go
@@ -25,7 +25,7 @@ func NewStartContainer(hostnames []string, image string, imagePullPolicy corev1.
 
 	var parts []string
 	for _, hostname := range hostnames {
-		parts = append(parts, fmt.Sprintf("curl --retry 3 -X PATCH -H 'Content-Type: application/json' http://%s/v1/status -d '%s'", net.JoinHostPort(hostname, "6565"), req))
+		parts = append(parts, fmt.Sprintf("curl --retry 3 -X PATCH -H 'Content-Type: application/json' http://%s/v1/status -d '%s' -s -w '\n{\"http_code\":%%{http_code},\"time_total\":%%{time_total},\"time_starttransfer\":%%{time_starttransfer},\"url\":\"%%{url_effective}\",\"remote_ip\":\"%%{remote_ip}\",\"errormsg\":\"%%{errormsg}\"}'", net.JoinHostPort(hostname, "6565"), req))
 	}
 
 	return corev1.Container{
diff --git a/pkg/resources/containers/curl_stop.go b/pkg/resources/containers/curl_stop.go
index ea3f43b..2dbbe41 100644
--- a/pkg/resources/containers/curl_stop.go
+++ b/pkg/resources/containers/curl_stop.go
@@ -25,7 +25,7 @@ func NewStopContainer(hostnames []string, image string, imagePullPolicy corev1.P
 
 	var parts []string
 	for _, hostname := range hostnames {
-		parts = append(parts, fmt.Sprintf("curl --retry 3 -X PATCH -H 'Content-Type: application/json' http://%s/v1/status -d '%s'", net.JoinHostPort(hostname, "6565"), req))
+		parts = append(parts, fmt.Sprintf("curl --retry 3 -X PATCH -H 'Content-Type: application/json' http://%s/v1/status -d '%s' -s -w '\n{\"http_code\":%%{http_code},\"time_total\":%%{time_total},\"time_starttransfer\":%%{time_starttransfer},\"url\":\"%%{url_effective}\",\"remote_ip\":\"%%{remote_ip}\",\"errormsg\":\"%%{errormsg}\"}'", net.JoinHostPort(hostname, "6565"), req))
 	}
 
 	return corev1.Container{
diff --git a/pkg/resources/jobs/helpers.go b/pkg/resources/jobs/helpers.go
index 0433402..da155ab 100644
--- a/pkg/resources/jobs/helpers.go
+++ b/pkg/resources/jobs/helpers.go
@@ -141,6 +141,7 @@ func getInitContainers(pod *v1alpha1.Pod, script *types.Script) []corev1.Contain
 			ImagePullPolicy: pod.ImagePullPolicy,
 			SecurityContext: &pod.ContainerSecurityContext,
 			RestartPolicy:   k6InitContainer.RestartPolicy,
+			Resources:       k6InitContainer.Resources,
 		}
 		initContainers = append(initContainers, initContainer)
 	}
diff --git a/pkg/resources/jobs/initializer.go b/pkg/resources/jobs/initializer.go
index 94bb0cf..0686c99 100644
--- a/pkg/resources/jobs/initializer.go
+++ b/pkg/resources/jobs/initializer.go
@@ -128,7 +128,8 @@ func NewInitializerJob(k6 *v1alpha1.TestRun, argLine string) (*batchv1.Job, erro
 							SecurityContext: &k6.GetSpec().Initializer.ContainerSecurityContext,
 						},
 					},
-					Volumes: volumes,
+					Volumes:           volumes,
+					PriorityClassName: k6.GetSpec().Initializer.PriorityClassName,
 				},
 			},
 		},
diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go
index 810dd84..e7d1c71 100644
--- a/pkg/resources/jobs/runner.go
+++ b/pkg/resources/jobs/runner.go
@@ -202,6 +202,7 @@ func NewRunnerJob(k6 *v1alpha1.TestRun, index int, tokenInfo *cloud.TokenInfo) (
 					}},
 					TerminationGracePeriodSeconds: &zero,
 					Volumes:                       volumes,
+					PriorityClassName:             k6.GetSpec().Runner.PriorityClassName,
 				},
 			},
 		},
diff --git a/pkg/resources/jobs/starter.go b/pkg/resources/jobs/starter.go
index 73ed75f..eed0960 100644
--- a/pkg/resources/jobs/starter.go
+++ b/pkg/resources/jobs/starter.go
@@ -96,6 +96,7 @@ func NewStarterJob(k6 *v1alpha1.TestRun, hostname []string) *batchv1.Job {
 							resourceRequirements,
 						),
 					},
+					PriorityClassName: k6.GetSpec().Starter.PriorityClassName,
 				},
 			},
 		},
diff --git a/pkg/types/k6cli.go b/pkg/types/k6cli.go
index cd2d437..cbf14b5 100644
--- a/pkg/types/k6cli.go
+++ b/pkg/types/k6cli.go
@@ -1,6 +1,9 @@
 package types
 
-import "strings"
+import (
+	"fmt"
+	"strings"
+)
 
 // CLI is an internal type to support k6 invocation in initialization stage.
 // Not all k6 commands allow the same set of arguments so CLI is an object
@@ -12,7 +15,7 @@ type CLI struct {
 	HasCloudOut bool
 }
 
-func ParseCLI(arguments string) *CLI {
+func ParseCLI(arguments string) (*CLI, error) {
 	lastArgV := func(start int, args []string) (end int) {
 		end = start
 		for end < len(args) {
@@ -25,7 +28,10 @@ func ParseCLI(arguments string) *CLI {
 		return
 	}
 
-	var cli CLI
+	var (
+		cli CLI
+		err error
+	)
 
 	args := strings.Split(arguments, " ")
 	i := 0
@@ -66,8 +72,11 @@ func ParseCLI(arguments string) *CLI {
 				cli.ArchiveArgs += strings.Join(args[i:end], " ")
 			}
 			i = end
+		} else {
+			err = fmt.Errorf("encountered an invalid value for k6 CLI argument: `%s`", args[i])
+			break
 		}
 	}
 
-	return &cli
+	return &cli, err
 }
diff --git a/pkg/types/script.go b/pkg/types/script.go
index 6e434fa..93a4833 100644
--- a/pkg/types/script.go
+++ b/pkg/types/script.go
@@ -59,15 +59,17 @@ func (s *Script) VolumeMount() []corev1.VolumeMount {
 
 	switch s.Type {
 
+	// VolumeClaim: mounts the volume at s.Path (default "/test").
 	case "VolumeClaim":
 		return []corev1.VolumeMount{
 			corev1.VolumeMount{
 				Name:      "k6-test-volume",
-				MountPath: "/test",
+				MountPath: s.Path,
 				ReadOnly:  s.ReadOnly,
 			},
 		}
 
+	// ConfigMap: always mounted at "/test" since keys cannot represent nested directories.
 	case "ConfigMap":
 		return []corev1.VolumeMount{
 			corev1.VolumeMount{



Test patch:
diff --git a/api/v1alpha1/testrun_types.go b/api/v1alpha1/testrun_types.go
index c889420..b14f0b0 100644
--- a/api/v1alpha1/testrun_types.go
+++ b/api/v1alpha1/testrun_types.go
@@ -52,6 +52,7 @@ type Pod struct {
 	InitContainers               []InitContainer                   `json:"initContainers,omitempty"`
 	Volumes                      []corev1.Volume                   `json:"volumes,omitempty"`
 	VolumeMounts                 []corev1.VolumeMount              `json:"volumeMounts,omitempty"`
+	PriorityClassName            string                            `json:"priorityClassName,omitempty"`
 }
 
 type InitContainer struct {
@@ -63,6 +64,7 @@ type InitContainer struct {
 	Args          []string                       `json:"args,omitempty"`
 	WorkingDir    string                         `json:"workingDir,omitempty"`
 	VolumeMounts  []corev1.VolumeMount           `json:"volumeMounts,omitempty"`
+	Resources     corev1.ResourceRequirements    `json:"resources,omitempty"`
 	RestartPolicy *corev1.ContainerRestartPolicy `json:"restartPolicy,omitempty"`
 }
 
@@ -143,6 +145,7 @@ type K6VolumeClaim struct {
 	// It is mounted as a `/test` folder to all k6 Pods.
 	Name string `json:"name"`
 	// Name of the file to execute (.js or .tar), stored on the Volume.
+	// Can include a path component (e.g., "subdir/script.js").
 	File string `json:"file,omitempty"`
 	// ReadOnly shows whether the volume should be mounted as `readOnly`.
 	ReadOnly bool `json:"readOnly,omitempty"`
@@ -203,26 +206,40 @@ func init() {
 	SchemeBuilder.Register(&TestRun{}, &TestRunList{})
 }
 
+func (k6 *TestRunSpec) Validate() error {
+	// Currently, we validate "manually" only arguments field.
+	_, err := types.ParseCLI(k6.Arguments)
+	return err
+}
+
 // Parse extracts Script data bits from K6 spec and performs basic validation
 func (k6 TestRunSpec) ParseScript() (*types.Script, error) {
 	spec := k6.Script
-	s := &types.Script{
-		Filename: "test.js",
-		Path:     "/test/",
-	}
+	s := &types.Script{}
 
+	// VolumeClaim: allow file to include a path component (e.g. "subdir/script.js").
 	if spec.VolumeClaim.Name != "" {
 		s.Name = spec.VolumeClaim.Name
 		if spec.VolumeClaim.File != "" {
-			s.Filename = spec.VolumeClaim.File
+			s.Path, s.Filename = filepath.Split(spec.VolumeClaim.File)
 			s.ReadOnly = spec.VolumeClaim.ReadOnly
 		}
+		if s.Path == "" {
+			s.Path = "/test/"
+		}
+		if s.Filename == "" {
+			s.Filename = "test.js"
+		}
 
 		s.Type = "VolumeClaim"
 		return s, nil
 	}
 
+	// ConfigMap: supports file name only (no path components)
 	if spec.ConfigMap.Name != "" {
+		s.Path = "/test/"
+		s.Filename = "test.js"
+
 		s.Name = spec.ConfigMap.Name
 
 		if spec.ConfigMap.File != "" {
diff --git a/internal/controller/testrun_controller.go b/internal/controller/testrun_controller.go
index adeb8a9..3ce2584 100644
--- a/internal/controller/testrun_controller.go
+++ b/internal/controller/testrun_controller.go
@@ -126,6 +126,14 @@ func (r *TestRunReconciler) reconcile(ctx context.Context, req ctrl.Request, log
 	case "":
 		log.Info("Initialize test")
 
+		if err := k6.GetSpec().Validate(); err != nil {
+			log.Error(err, "Invalid TestRun")
+			log.Info("Changing stage of TestRun status to error")
+			k6.GetStatus().Stage = "error"
+			_, err := r.UpdateStatus(ctx, k6, log)
+			return ctrl.Result{}, err
+		}
+
 		v1alpha1.Initialize(k6)
 
 		if _, err := r.UpdateStatus(ctx, k6, log); err != nil {
@@ -432,15 +440,10 @@ func (r *TestRunReconciler) ShouldAbort(ctx context.Context, k6 *v1alpha1.TestRu
 
 	status, err := cloud.GetTestRunState(r.k6CloudClient, k6.TestRunID(), log)
 	if err != nil {
-		log.Error(err, "Failed to get test run state.")
 		return false
 	}
 
-	isAborted := status.Aborted()
-
-	log.Info(fmt.Sprintf("Received test run status %v", status))
-
-	return isAborted
+	return status.Aborted()
 }
 
 func (r *TestRunReconciler) createClient(ctx context.Context, k6 *v1alpha1.TestRun, log logr.Logger) (bool, error) {
diff --git a/pkg/cloud/test_runs.go b/pkg/cloud/test_runs.go
index 20b69c5..2c954fe 100644
--- a/pkg/cloud/test_runs.go
+++ b/pkg/cloud/test_runs.go
@@ -24,12 +24,16 @@ type TestRunPoller struct {
 }
 
 func NewTestRunPoller(host, token, plzName string, logger logr.Logger) *TestRunPoller {
-	logrusLogger := &logrus.Logger{
+	// We need two loggers here because of logrus dependency in cloudapi.
+	// This will have a re-visit during or after https://github.com/grafana/k6-operator/issues/571
+	l := &logrus.Logger{
 		Out:       os.Stdout,
 		Formatter: new(logrus.TextFormatter),
 		Hooks:     make(logrus.LevelHooks),
 		Level:     logrus.InfoLevel,
 	}
+	logrusLogger := l.WithFields(logrus.Fields{"k6_cloud_host": host})
+	logger = logger.WithValues("k6_cloud_host", host)
 
 	testRunsCh := make(chan string)
 
@@ -110,35 +114,46 @@ func GetTestRunData(client *cloudapi.Client, refID string) (*TestRunData, error)
 }
 
 // called by TestRun controller
-func GetTestRunState(client *cloudapi.Client, refID string, log logr.Logger) (TestRunStatus, error) {
-	url := fmt.Sprintf("%s/loadtests/v4/test_runs(%s)?$select=id,run_status", ApiURL(client.BaseURL()), refID)
+// If there's an error, it'll be logged.
+func GetTestRunState(client *cloudapi.Client, refID string, logger logr.Logger) (TestRunStatus, error) {
+	host := ApiURL(client.BaseURL())
+	logger = logger.WithValues("k6_cloud_host", host)
+
+	url := fmt.Sprintf("%s/loadtests/v4/test_runs(%s)?$select=id,run_status", host, refID)
 	trData, err := getTestRun(client, url)
 	if err != nil {
+		logger.Error(err, "Failed to get test run state.")
 		return TestRunStatus(cloudapi.RunStatusRunning), err
 	}
 
-	return TestRunStatus(trData.RunStatus), nil
+	status := TestRunStatus(trData.RunStatus)
+	logger.Info(fmt.Sprintf("Received test run status %v", status))
+
+	return status, nil
 }
 
 // called by TestRun controller
 // If there's an error, it'll be logged.
-func SendTestRunEvents(client *cloudapi.Client, refID string, log logr.Logger, events *Events) {
+func SendTestRunEvents(client *cloudapi.Client, refID string, logger logr.Logger, events *Events) {
 	if len(*events) == 0 {
 		return
 	}
 
-	url := fmt.Sprintf("%s/orchestrator/v1/testruns/%s/events", strings.TrimSuffix(client.BaseURL(), "/v1"), refID)
+	host := strings.TrimSuffix(client.BaseURL(), "/v1")
+	logger = logger.WithValues("k6_cloud_host", host)
+
+	url := fmt.Sprintf("%s/orchestrator/v1/testruns/%s/events", host, refID)
 	req, err := client.NewRequest("POST", url, events)
 
 	if err != nil {
-		log.Error(err, fmt.Sprintf("Failed to create events HTTP request %+v", events))
+		logger.Error(err, fmt.Sprintf("Failed to create events HTTP request %+v", events))
 		return
 	}
 
-	log.Info(fmt.Sprintf("Sending events to k6 Cloud %+v", *events))
+	logger.Info(fmt.Sprintf("Sending events to k6 Cloud %+v", *events))
 
 	// status code is checked in Do
 	if err = client.Do(req, nil); err != nil {
-		log.Error(err, fmt.Sprintf("Failed to send events %+v", events))
+		logger.Error(err, fmt.Sprintf("Failed to send events %+v", events))
 	}
 }
diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go
index e5d7c17..44c1500 100644
--- a/pkg/resources/jobs/runner_test.go
+++ b/pkg/resources/jobs/runner_test.go
@@ -1747,3 +1747,123 @@ func TestNewRunnerJobPLZTestRun(t *testing.T) {
 		t.Errorf("NewRunnerJob returned unexpected data, diff: %s", diff)
 	}
 }
+
+func TestNewRunnerJobPriorityClassName(t *testing.T) {
+
+	script := &types.Script{
+		Name:     "test",
+		Filename: "thing.js",
+		Type:     "ConfigMap",
+	}
+
+	var zero int64 = 0
+	automountServiceAccountToken := true
+
+	expectedLabels := map[string]string{
+		"app":    "k6",
+		"k6_cr":  "test",
+		"runner": "true",
+		"label1": "awesome",
+	}
+
+	expectedOutcome := &batchv1.Job{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-1",
+			Namespace: "test",
+			Labels:    expectedLabels,
+			Annotations: map[string]string{
+				"awesomeAnnotation": "dope",
+			},
+		},
+		Spec: batchv1.JobSpec{
+			BackoffLimit: new(int32),
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: expectedLabels,
+					Annotations: map[string]string{
+						"awesomeAnnotation": "dope",
+					},
+				},
+				Spec: corev1.PodSpec{
+					Hostname:                     "test-1",
+					RestartPolicy:                corev1.RestartPolicyNever,
+					Affinity:                     nil,
+					NodeSelector:                 nil,
+					Tolerations:                  nil,
+					TopologySpreadConstraints:    nil,
+					ServiceAccountName:           "default",
+					AutomountServiceAccountToken: &automountServiceAccountToken,
+					SecurityContext:              &corev1.PodSecurityContext{},
+					Containers: []corev1.Container{{
+						Image:           "grafana/k6:latest",
+						ImagePullPolicy: "",
+						Name:            "k6",
+						Command:         []string{"k6", "run", "--quiet", "/test/test.js", "--address=0.0.0.0:6565", "--paused", "--tag", "instance_id=1", "--tag", "job_name=test-1"},
+						Env:             []corev1.EnvVar{},
+						Resources:       corev1.ResourceRequirements{},
+						VolumeMounts:    script.VolumeMount(),
+						Ports:           []corev1.ContainerPort{{ContainerPort: 6565}},
+						LivenessProbe: &corev1.Probe{
+							ProbeHandler: corev1.ProbeHandler{
+								HTTPGet: &corev1.HTTPGetAction{
+									Path:   "/v1/status",
+									Port:   intstr.IntOrString{IntVal: 6565},
+									Scheme: "HTTP",
+								},
+							},
+						},
+						ReadinessProbe: &corev1.Probe{
+							ProbeHandler: corev1.ProbeHandler{
+								HTTPGet: &corev1.HTTPGetAction{
+									Path:   "/v1/status",
+									Port:   intstr.IntOrString{IntVal: 6565},
+									Scheme: "HTTP",
+								},
+							},
+						},
+						SecurityContext: &corev1.SecurityContext{},
+					}},
+					TerminationGracePeriodSeconds: &zero,
+					Volumes:                       script.Volume(),
+					PriorityClassName:             "high-priority",
+				},
+			},
+		},
+	}
+
+	k6 := &v1alpha1.TestRun{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test",
+			Namespace: "test",
+		},
+		Spec: v1alpha1.TestRunSpec{
+
+			Script: v1alpha1.K6Script{
+				ConfigMap: v1alpha1.K6Configmap{
+					Name: "test",
+					File: "test.js",
+				},
+			},
+			Runner: v1alpha1.Pod{
+				Metadata: v1alpha1.PodMetadata{
+					Labels: map[string]string{
+						"label1": "awesome",
+					},
+					Annotations: map[string]string{
+						"awesomeAnnotation": "dope",
+					},
+				},
+				PriorityClassName: "high-priority",
+			},
+		},
+	}
+
+	job, err := NewRunnerJob(k6, 1, cloud.NewTokenInfo("", ""))
+	if err != nil {
+		t.Errorf("NewRunnerJob errored, got: %v", err)
+	}
+
+	if diff := deep.Equal(job, expectedOutcome); diff != nil {
+		t.Errorf("NewRunnerJob returned unexpected data, diff: %s", diff)
+	}
+}
diff --git a/pkg/types/k6cli_test.go b/pkg/types/k6cli_test.go
index 8135b07..47193d3 100644
--- a/pkg/types/k6cli_test.go
+++ b/pkg/types/k6cli_test.go
@@ -8,14 +8,16 @@ import (
 
 func Test_ParseCLI(t *testing.T) {
 	tests := []struct {
-		name    string
-		argLine string
-		cli     CLI
+		name           string
+		argLine        string
+		cli            CLI
+		validArguments bool
 	}{
 		{
 			"EmptyArgs",
 			"",
 			CLI{},
+			false,
 		},
 		{
 			"ShortArchiveArgs",
@@ -23,6 +25,7 @@ func Test_ParseCLI(t *testing.T) {
 			CLI{
 				ArchiveArgs: "-u 10 -d 5",
 			},
+			false,
 		},
 		{
 			"LongArchiveArgs",
@@ -30,6 +33,7 @@ func Test_ParseCLI(t *testing.T) {
 			CLI{
 				ArchiveArgs: "--vus 10 --duration 5",
 			},
+			false,
 		},
 		{
 			"ShortNonArchiveArg",
@@ -37,6 +41,7 @@ func Test_ParseCLI(t *testing.T) {
 			CLI{
 				ArchiveArgs: "-u 10 -d 5",
 			},
+			false,
 		},
 		{
 			"LongNonArchiveArgs",
@@ -44,6 +49,7 @@ func Test_ParseCLI(t *testing.T) {
 			CLI{
 				ArchiveArgs: "--vus 10 --duration 5",
 			},
+			false,
 		},
 		{
 			"OutWithoutCloudArgs",
@@ -52,6 +58,7 @@ func Test_ParseCLI(t *testing.T) {
 				ArchiveArgs: "--vus 10",
 				HasCloudOut: false,
 			},
+			false,
 		},
 		{
 			"OutWithCloudArgs",
@@ -60,6 +67,7 @@ func Test_ParseCLI(t *testing.T) {
 				ArchiveArgs: "--vus 10",
 				HasCloudOut: true,
 			},
+			false,
 		},
 		{
 			"VerboseOutWithCloudArgs",
@@ -68,6 +76,7 @@ func Test_ParseCLI(t *testing.T) {
 				ArchiveArgs: "--vus 10",
 				HasCloudOut: true,
 			},
+			false,
 		},
 		{
 			"OmitLogOutput",
@@ -76,6 +85,13 @@ func Test_ParseCLI(t *testing.T) {
 				ArchiveArgs: "--no-thresholds",
 				HasCloudOut: true,
 			},
+			false,
+		},
+		{
+			"InvalidArguments",
+			`run this-argument-does-not-matter.js -o json`,
+			CLI{},
+			true,
 		},
 	}
 
@@ -83,8 +99,9 @@ func Test_ParseCLI(t *testing.T) {
 		test := test
 		t.Run(test.name, func(t *testing.T) {
 			t.Parallel()
-			cli := ParseCLI(test.argLine)
+			cli, err := ParseCLI(test.argLine)
 
+			assert.Equal(t, test.validArguments, err != nil)
 			assert.Equal(t, test.cli.ArchiveArgs, cli.ArchiveArgs)
 			assert.Equal(t, test.cli.HasCloudOut, cli.HasCloudOut)
 		})