Expect

5 min read

spec.expect is an ordered list of assertion checkpoints. Each checkpoint declares a lifecycle trigger (after:), a timeout, and a set of resource or command assertions. All assertions in a checkpoint must pass for the checkpoint to pass.


Checkpoint structure

expect:
  - name: Deployment created and ready
    after: cr-applied
    timeout: 60s
    resources:
      - kind: Deployment
        namespace: default
        ready: true
    commands:
      - run: "kubectl get deploy -n default -o name"
        outputContains: "hello-website"
FieldRequiredDescription
nameyes*Printed in the results table. Not required when include: is set.
afteryesLifecycle phase that must have occurred.
timeoutyesMaximum wait time (Go duration: 30s, 2m, 90s).
waitnoDuration to sleep before the polling loop starts (Go duration: 5s, 30s). Useful when the previous step triggers an async operation that needs time to propagate before assertions are meaningful.
resourcesnoResource state assertions, polled until passing.
commandsnoShell command assertions, run in the same polling loop.
kubectlnoStructured kubectl subcommand assertions. See kubectl block.
onFailurenoDiagnostic kubectl and shell commands to run and print when this specific checkpoint fails. See Per-expectation onFailure.
includenoPath to a YAML file containing a bare list of checkpoints to expand in place. See Composing expectations.

after

ValueWhen it triggers
setup-completeAfter all setup steps finish, before the CR is applied. Use for Kubernetes workloads that are not operators — where setup is the thing under test, not a CR lifecycle.
cr-appliedAfter the CR has been applied and the initial reconcile has started.
cr-deletedAfter the CR has been deleted and finalizer cleanup has run.

resources

A list of Kubernetes resource state checks. All must pass for the checkpoint to pass.

resources:
  - kind: Deployment
    name: hello-website
    namespace: default
    ready: true

  - kind: Service
    name: hello-website-svc
    namespace: default

  - kind: Website
    name: hello-website
    namespace: default
    count: 0     # must not exist (cleanup check)
FieldRequiredDescription
kindyesKubernetes resource kind: Deployment, Service, Pod, Secret, etc.
namenoExact name. Omit to match any resource of this kind in the namespace.
namespacenoNamespace. Defaults to default.
readynotrue waits for available/ready condition. Deployment: availableReplicas == replicas. Pod: Ready condition true.
countnoExact expected count. 0 asserts the resource does not exist — use in cr-deleted checkpoints to verify cleanup.

commands

Shell commands run in the same polling loop as resources. Useful for assertions that go beyond resource existence — health endpoints, data validation, connectivity checks.

commands:
  - run: "kubectl exec -n default deploy/hello-website -- wget -qO- localhost:80"
    exitCode: 0
    outputContains: "nginx"

  - run: "kubectl get secret -n platform database-credentials -o name"
    exitCode: 0
FieldRequiredDescription
runyesShell command executed via sh -c.
exitCodenoExpected exit code. Default 0 (success). Set non-zero to assert the command must fail — useful for admission webhook rejection tests.
outputContainsnoThe combined stdout+stderr must contain this substring.
outputNotContainsnoThe combined stdout+stderr must not contain this substring.
equalsnoOutput (trimmed) must exactly match this string.
notEqualsnoOutput must not exactly match this string.
greaterThannoOutput (trimmed, parsed as a number) must be greater than this value.
lessThannoOutput (trimmed, parsed as a number) must be less than this value.

Per-expectation onFailure

A checkpoint can declare its own onFailure: block. When that specific checkpoint fails, its onFailure diagnostics run immediately — before the next checkpoint is evaluated. This lets you capture cluster state at the moment of failure, when it is most informative.

expect:
  - name: Both probes reach Ready status
    after: cr-applied
    timeout: 120s
    kubectl:
      get:
        - kind: E2EProbe
          name: my-probe-server
          namespace: default
          field: .status.phase
          equals: Ready
    onFailure:
      kubectl:
        get:
          - kind: E2EProbe
            name: my-probe-server
            namespace: default
        describe:
          - kind: Deployment
            name: my-probe-server
            namespace: default
      commands:
        - kubectl get pods -n default -o wide
FieldDescription
kubectlAccepts the full kubectl: DSL (get, logs, describe, events, exec). Assertion fields are ignored — output is printed.
commandsList of shell strings run via sh -c. Output is printed to the terminal.

The per-expectation onFailure is complementary to spec.onFailure. The difference:

When it runs
expect[].onFailureImmediately after that checkpoint fails — cluster state reflects the failure context
spec.onFailureOnce at the end, after all expectations complete — useful for a global summary

Composing expectations with include:

Large test suites can be split across files. An include: entry is replaced in place by the checkpoints in the referenced file — position in the list determines where the expanded checkpoints appear in the run order. Place an include: at the top for setup checks, in the middle for shared assertions, or at the end for cleanup. The file uses expect: as its root key, mirroring the field it slots into.

# e2e.yaml
expect:
  - include: ./infra-ready.yaml     # expands here — runs first
  - name: Operator-specific check
    after: cr-applied
    timeout: 30s
    resources:
      - kind: MyOperator
        name: my-resource
  - include: ./cleanup.yaml         # expands here — runs last
# infra-ready.yaml
expect:
  - name: CRD registered
    after: cr-applied
    timeout: 30s
    kubectl:
      get:
        - kind: CustomResourceDefinition
          name: myoperators.example.com
          field: status.conditions[0].type
          equals: Established

Paths are resolved relative to the e2e.yaml that contains the include: entry. Nested includes (a file that itself includes another) are not supported.

Use ork validate to confirm the expansion and see where each checkpoint landed:

● my-operator-e2e

    CRD registered                   after: cr-applied    timeout: 30s
    Operator-specific check          after: cr-applied    timeout: 30s
    Cleanup verified                 after: cr-deleted    timeout: 60s

────────────────────────────────────────────────────────────
3 expectation(s) valid

The checkpoint list is the fully-expanded run order — what you see is what runs.


→ Back: 02-setup | Schema index