Repository: smartcontractkit/chainlink
Pull request #16930: [DX-208] read cron/cre cli binaries from local files,
Author: login: Tofel
Created: 2025-03-25T11:46:11Z | Merged: 2025-03-25T14:07:58Z

but in CI allow to download them from GH.



Diff:
diff --git a/system-tests/lib/cre/capabilities/download.go b/system-tests/lib/cre/capabilities/download.go
index 23644c872d4..452de0ed91e 100644
--- a/system-tests/lib/cre/capabilities/download.go
+++ b/system-tests/lib/cre/capabilities/download.go
@@ -2,6 +2,7 @@ package capabilities
 
 import (
 	"os"
+	"path/filepath"
 
 	"github.com/smartcontractkit/chainlink-testing-framework/lib/client"
 )
@@ -24,5 +25,10 @@ func DownloadCapabilityFromRelease(ghToken, version, assetFileName string) (stri
 		return "", err
 	}
 
-	return fileName, nil
+	absPath, err := filepath.Abs(fileName)
+	if err != nil {
+		return "", err
+	}
+
+	return absPath, nil
 }
diff --git a/system-tests/tests/smoke/cre/environment-capabilities-don-ci.toml b/system-tests/tests/smoke/cre/environment-capabilities-don-ci.toml
index c6ac68f93b5..ac640735991 100644
--- a/system-tests/tests/smoke/cre/environment-capabilities-don-ci.toml
+++ b/system-tests/tests/smoke/cre/environment-capabilities-don-ci.toml
@@ -18,8 +18,8 @@
   should_compile_new_workflow = false
 
   [workflow_config.dependencies]
-  capabilities_version = "v1.0.2-alpha"
-  cre_cli_version = "v0.1.5"
+  cron_capability_binary_path = "injected-at-runtime"
+  cre_cli_binary_path = "injected-at-runtime"
 
   [workflow_config.compiled_config]
     binary_url = "https://gist.githubusercontent.com/Tofel/73d703157bafe65ab51f7e619c589091/raw/cb7b2a56b37e333fe0bdce07b79538c4ce332f5f/binary.wasm.br"
@@ -57,7 +57,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -75,7 +74,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -93,7 +91,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -111,7 +108,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
diff --git a/system-tests/tests/smoke/cre/environment-capabilities-don-crib.toml b/system-tests/tests/smoke/cre/environment-capabilities-don-crib.toml
index 0fe71d3a04b..002ce1dc547 100644
--- a/system-tests/tests/smoke/cre/environment-capabilities-don-crib.toml
+++ b/system-tests/tests/smoke/cre/environment-capabilities-don-crib.toml
@@ -29,8 +29,8 @@
   workflow_folder_location = "../../../../../proof-of-reserves-workflow-e2e-test"
 
   [workflow_config.dependencies]
-  capabilities_version = "v1.0.2-alpha"
-  cre_cli_version = "v0.1.5"
+  cron_capability_binary_path = "not-supported-currently"
+  cre_cli_binary_path = "./cre_v0.1.5_darwin_arm64"
 
   [workflow_config.compiled_config]
     binary_url = "https://gist.githubusercontent.com/Tofel/73d703157bafe65ab51f7e619c589091/raw/cb7b2a56b37e333fe0bdce07b79538c4ce332f5f/binary.wasm.br"
diff --git a/system-tests/tests/smoke/cre/environment-capabilities-don.toml b/system-tests/tests/smoke/cre/environment-capabilities-don.toml
index 499834bf5a8..63719dd1a18 100644
--- a/system-tests/tests/smoke/cre/environment-capabilities-don.toml
+++ b/system-tests/tests/smoke/cre/environment-capabilities-don.toml
@@ -22,8 +22,8 @@
   workflow_folder_location = "../../../../../proof-of-reserves-workflow-e2e-test"
 
   [workflow_config.dependencies]
-  capabilities_version = "v1.0.2-alpha"
-  cre_cli_version = "v0.1.5"
+  cron_capability_binary_path = "./cron"
+  cre_cli_binary_path = "./cre_v0.1.5_darwin_arm64"
 
   [workflow_config.compiled_config]
     binary_url = "https://gist.githubusercontent.com/Tofel/73d703157bafe65ab51f7e619c589091/raw/cb7b2a56b37e333fe0bdce07b79538c4ce332f5f/binary.wasm.br"
@@ -62,7 +62,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -81,7 +80,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -100,7 +98,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -119,7 +116,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
diff --git a/system-tests/tests/smoke/cre/environment-gateway-don-ci.toml b/system-tests/tests/smoke/cre/environment-gateway-don-ci.toml
index 859440598c8..44bc9dd46de 100644
--- a/system-tests/tests/smoke/cre/environment-gateway-don-ci.toml
+++ b/system-tests/tests/smoke/cre/environment-gateway-don-ci.toml
@@ -21,8 +21,8 @@
   should_compile_new_workflow = false
 
   [workflow_config.dependencies]
-  capabilities_version = "v1.0.2-alpha"
-  cre_cli_version = "v0.1.5"
+  cron_capability_binary_path = "injected-at-runtime"
+  cre_cli_binary_path = "injected-at-runtime"
 
   [workflow_config.compiled_config]
     binary_url = "https://gist.githubusercontent.com/Tofel/0a595b92554b375415925749cc126969/raw/cb7b2a56b37e333fe0bdce07b79538c4ce332f5f/binary.wasm.br"
@@ -62,7 +62,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -80,7 +79,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -98,7 +96,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -116,7 +113,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
diff --git a/system-tests/tests/smoke/cre/environment-gateway-don.toml b/system-tests/tests/smoke/cre/environment-gateway-don.toml
index be6987be7f7..96dedb4019f 100644
--- a/system-tests/tests/smoke/cre/environment-gateway-don.toml
+++ b/system-tests/tests/smoke/cre/environment-gateway-don.toml
@@ -26,8 +26,8 @@
   workflow_folder_location = "../../../../../proof-of-reserves-workflow-e2e-test"
 
   [workflow_config.dependencies]
-  capabilities_version = "v1.0.2-alpha"
-  cre_cli_version = "v0.1.5"
+  cron_capability_binary_path = "./cron"
+  cre_cli_binary_path = "./cre_v0.1.5_darwin_arm64"
 
   [workflow_config.compiled_config]
     binary_url = "https://gist.githubusercontent.com/Tofel/73d703157bafe65ab51f7e619c589091/raw/cb7b2a56b37e333fe0bdce07b79538c4ce332f5f/binary.wasm.br"
@@ -67,7 +67,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -86,7 +85,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -105,7 +103,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -124,7 +121,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
diff --git a/system-tests/tests/smoke/cre/environment-one-don-ci.toml b/system-tests/tests/smoke/cre/environment-one-don-ci.toml
index 3a8f0c280b6..e2fa51f4b7d 100644
--- a/system-tests/tests/smoke/cre/environment-one-don-ci.toml
+++ b/system-tests/tests/smoke/cre/environment-one-don-ci.toml
@@ -21,8 +21,8 @@
   should_compile_new_workflow = false
 
   [workflow_config.dependencies]
-  capabilities_version = "v1.0.2-alpha"
-  cre_cli_version = "v0.1.5"
+  cron_capability_binary_path = "injected-at-runtime"
+  cre_cli_binary_path = "injected-at-runtime"
 
   [workflow_config.compiled_config]
     binary_url = "https://gist.githubusercontent.com/Tofel/0a595b92554b375415925749cc126969/raw/cb7b2a56b37e333fe0bdce07b79538c4ce332f5f/binary.wasm.br"
@@ -62,7 +62,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -80,7 +79,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -98,7 +96,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -116,7 +113,6 @@
 
     [nodesets.node_specs.node]
       image = "injected-at-runtime"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
diff --git a/system-tests/tests/smoke/cre/environment-one-don.toml b/system-tests/tests/smoke/cre/environment-one-don.toml
index 07ca5ff8476..ef80555214a 100644
--- a/system-tests/tests/smoke/cre/environment-one-don.toml
+++ b/system-tests/tests/smoke/cre/environment-one-don.toml
@@ -26,8 +26,8 @@
   workflow_folder_location = "../../../../../proof-of-reserves-workflow-e2e-test"
 
   [workflow_config.dependencies]
-  capabilities_version = "v1.0.2-alpha"
-  cre_cli_version = "v0.1.5"
+  cron_capability_binary_path = "./cron"
+  cre_cli_binary_path = "./cre_v0.1.5_darwin_arm64"
 
   [workflow_config.compiled_config]
     binary_url = "https://gist.githubusercontent.com/Tofel/73d703157bafe65ab51f7e619c589091/raw/cb7b2a56b37e333fe0bdce07b79538c4ce332f5f/binary.wasm.br"
@@ -67,7 +67,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -86,7 +85,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -105,7 +103,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
@@ -124,7 +121,6 @@
     [nodesets.node_specs.node]
       docker_ctx = "../../../.."
       docker_file = "plugins/chainlink.Dockerfile"
-      capabilities = ["./amd64_cron"]
       user_config_overrides = """
       [Feature]
 			LogPoller = true
diff --git a/system-tests/tests/smoke/cre/guidelines.md b/system-tests/tests/smoke/cre/guidelines.md
index 7e7685e8fe2..234d0fc8f1f 100644
--- a/system-tests/tests/smoke/cre/guidelines.md
+++ b/system-tests/tests/smoke/cre/guidelines.md
@@ -48,22 +48,33 @@ The test requires several environment variables. Below is a launch configuration
   "type": "go",
   "request": "launch",
   "mode": "test",
-  "program": "${workspaceFolder}/integration-tests/smoke/capabilities",
+  "program": "${workspaceFolder}/system-tests/tests/smoke/cre",
   "env": {
-    "CTF_CONFIGS": "environment.toml",
+    "CTF_CONFIGS": "environment-one-don.toml",
     "PRIVATE_KEY": "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
-    "GITHUB_GIST_API_TOKEN": "your-gist-read:write-fpat-token",
-    "GITHUB_CAP_API_TOKEN": "your-capabilities-repo-content-read-fpat-token"
   },
   "args": [
     "-test.run",
-    "TestKeystoneWithOCR3Workflow"
+    "TestCRE_OCR3_PoR_Workflow_SingleDon_MockedPrice"
   ]
 }
 ```
 
-- **`GITHUB_READ_TOKEN`**: Required for downloading the `cron` capability binary and CRE CLI (if enabled). Requires `content:read` permission for `smartcontractkit/capabilities` and `smartcontractkit/dev-platform` repositories. Use a fine-grained personal access token (PAT) tied to the **organization’s GitHub account**.
-- **`GIST_WRITE_TOKEN`**: Required only for compiling and uploading a new workflow. It needs `gist:read:write` permissions and should be a fine-grained PAT **tied to your personal GitHub account**.
+You might also need to adjust the TOML configuration file used by your test, so that it points to correct location of two binaries:
+* `cron` -- cron capability binary for AMD platform that lives in [smartcontractkit/capabilities](https://github.com/smartcontractkit/capabilities)
+* `CRE CLI` -- CLI binary compiled for architecture of your host machine, that lives in [smartcontractkit/dev-platform](https://github.com/smartcontractkit/dev-platform)
+
+The easiest way to go about it would be to download them from releases page.
+
+TOML config part that needs to be adjusted is the following one:
+```toml
+  [workflow_config.dependencies]
+  # v1.0.2-alpha should work
+  cron_capability_binary_path = "./cron"
+  cre_cli_binary_path = "./cre_v0.1.5_darwin_arm64"
+```
+
+In CI the flow is a bit different, because we generate one-time access tokens to these two repositories and testing code downloads required assets on its own.
 
 Test also expects you to have the Job Distributor image available locally. By default, `environment-*.toml`'s expect image tagged as `job-distributor:0.9.0`. The easiest way to get it, is to clone the Job Distributor repository and build it locally with:
 ```bash
@@ -329,12 +340,16 @@ Let's assume we want to add a capability that represents writing to Aptos chain.
 
 ### Copying the Binary to the Container
 
-The test configuration is defined in a TOML file (e.g. `environment-*.toml`), which specifies properties for Chainlink nodes. The `capabilities` property of the `node_specs.node` determines which binaries are copied to the container:
+The test configuration is defined in a TOML file (e.g. `environment-*.toml`), which specifies properties for Chainlink nodes. The `capabilities` property of the `node_specs.node` determines which binaries are copied to the container.
+We use Go code to programmatically modify that `node_spec` based on the capabilities each DON has to copy required binary files to the container during runtime.
+
+Config part that's effectively modified:
 
 ```toml
   [[nodeset.node_specs]]
 
     [nodeset.node_specs.node]
+      # it's best if you don't use this directive in TOML and instead copy your capability using Go code, like we do for cron
       capabilities = ["./aptos_linux_amd64"]
 ```
 
@@ -364,16 +379,6 @@ const (
 
 This ensures the TOML configuration correctly maps each DON to its capabilities.
 
-Optionally, add the new flag to the default capabilities used in a single DON setup:
-
-```go
-var (
-	// Add new capabilities here as well, if single DON should have them by default
-	SingleDonFlags = []string{"workflow", "capabilities", "ocr3", "cron", "custom-compute", "write-evm", "write-aptos"}
-                                                                                                        // <------------ New entry
-)
-```
-
 Now that the flag is defined, let's configure the nodes and jobs.
 
 #### Defining Additional Node Configuration
@@ -442,7 +447,7 @@ The final step is adding support for registration of the capability with the Cap
 if hasFlag(donTopology.Flags, WriteAptosCapability) {
   capabilities = append(capabilities, keystone_changeset.DONCapabilityWithConfig{
     Capability: kcr.CapabilitiesRegistryCapability{
-      LabelledName:   "write_aptos-testnet",          // <------- Ensure correct name
+      LabelledName:   "write_aptos-testnet",          // <------- Ensure correct name (it might be dynamic and depend on things like chainID)
       Version:        "1.0.0",                        // <------- Ensure correct version
       CapabilityType: 3, // TARGET
       ResponseType:   1, // OBSERVATION_IDENTICAL
@@ -501,7 +506,7 @@ workflowConfig := PoRWorkflowConfig{
 ---
 
 ### Workflow Secrets
-Currently, workflow secrets are **not supported**.
+Currently, workflow secrets are **not supported** yet.
 
 ---
 
diff --git a/system-tests/tests/smoke/cre/por_test.go b/system-tests/tests/smoke/cre/por_test.go
index 24f5b95c7f7..f9ae2175dd9 100644
--- a/system-tests/tests/smoke/cre/por_test.go
+++ b/system-tests/tests/smoke/cre/por_test.go
@@ -49,8 +49,10 @@ import (
 	keystoneporconfig "github.com/smartcontractkit/chainlink/system-tests/lib/cre/don/config/por"
 	libjobs "github.com/smartcontractkit/chainlink/system-tests/lib/cre/don/jobs"
 	keystonepor "github.com/smartcontractkit/chainlink/system-tests/lib/cre/don/jobs/por"
+	libnode "github.com/smartcontractkit/chainlink/system-tests/lib/cre/don/node"
 	keystonesecrets "github.com/smartcontractkit/chainlink/system-tests/lib/cre/don/secrets"
 	libenv "github.com/smartcontractkit/chainlink/system-tests/lib/cre/environment"
+	"github.com/smartcontractkit/chainlink/system-tests/lib/cre/flags"
 	keystonetypes "github.com/smartcontractkit/chainlink/system-tests/lib/cre/types"
 	libcrecli "github.com/smartcontractkit/chainlink/system-tests/lib/crecli"
 	keystoneporcrecli "github.com/smartcontractkit/chainlink/system-tests/lib/crecli/por"
@@ -60,7 +62,7 @@ import (
 )
 
 const (
-	cronCapabilityAssetFile            = "amd64_cron"
+	cronCapabilityAssetFile            = "cron"
 	ghReadTokenEnvVarName              = "GITHUB_READ_TOKEN"
 	E2eJobDistributorImageEnvVarName   = "E2E_JD_IMAGE"
 	E2eJobDistributorVersionEnvVarName = "E2E_JD_VERSION"
@@ -147,12 +149,18 @@ func init() {
 	}
 }
 
-// Defines relases/versions of test dependencies that will be downloaded from Github
+// Defines the location of the binary files that are required to run the test
+// When test runs in CI hardcoded versions will be downloaded instead
 type DependenciesConfig struct {
-	CapabiltiesVersion string `toml:"capabilities_version" validate:"required"`
-	CRECLIVersion      string `toml:"cre_cli_version" validate:"required"`
+	CronCapabilityBinaryPath string `toml:"cron_capability_binary_path" validate:"required"`
+	CRECLIBinaryPath         string `toml:"cre_cli_binary_path" validate:"required"`
 }
 
+const (
+	CronBinaryVersion   = "v1.0.2-alpha"
+	CRECLIBinaryVersion = "v0.1.5"
+)
+
 // Defines the location of already compiled workflow binary and config files
 // They will be used if WorkflowConfig.ShouldCompileNewWorkflow is `false`
 // Otherwise test will compile and upload a new workflow
@@ -204,34 +212,24 @@ func validateEnvVars(t *testing.T, in *TestConfig) {
 	}
 }
 
-type binaryDownloadOutput struct {
-	creCLIAbsPath string
-}
-
 // this is a small hack to avoid changing the reusable workflow, which doesn't allow to run any pre-execution hooks
-func downloadBinaryFiles(in *TestConfig) (*binaryDownloadOutput, error) {
-	var ghReadToken string
-	if os.Getenv("CI") == "true" {
-		ghReadToken = ctfconfig.MustReadEnvVar_String(ghReadTokenEnvVarName)
-	} else {
-		ghReadToken = os.Getenv(ghReadTokenEnvVarName)
-	}
+func downloadBinaryFiles(in *TestConfig) error {
+	ghReadToken := ctfconfig.MustReadEnvVar_String(ghReadTokenEnvVarName)
 
-	_, err := keystonecapabilities.DownloadCapabilityFromRelease(ghReadToken, in.WorkflowConfig.DependenciesConfig.CapabiltiesVersion, cronCapabilityAssetFile)
+	var err error
+	in.WorkflowConfig.DependenciesConfig.CronCapabilityBinaryPath, err = keystonecapabilities.DownloadCapabilityFromRelease(ghReadToken, CronBinaryVersion, cronCapabilityAssetFile)
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to download cron capability. Make sure token has content:read permissions to the capabilities repo")
+		return errors.Wrap(err, "failed to download cron capability. Make sure token has content:read permissions to the capabilities repo")
 	}
 
-	output := &binaryDownloadOutput{}
-
 	if in.WorkflowConfig.UseCRECLI {
-		output.creCLIAbsPath, err = libcrecli.DownloadAndInstallChainlinkCLI(ghReadToken, in.WorkflowConfig.DependenciesConfig.CRECLIVersion)
+		in.WorkflowConfig.DependenciesConfig.CRECLIBinaryPath, err = libcrecli.DownloadAndInstallChainlinkCLI(ghReadToken, CRECLIBinaryVersion)
 		if err != nil {
-			return nil, errors.Wrap(err, "failed to download and install CRE CLI. Make sure token has content:read permissions to the dev-platform repo")
+			return errors.Wrap(err, "failed to download and install CRE CLI. Make sure token has content:read permissions to the dev-platform repo")
 		}
 	}
 
-	return output, nil
+	return nil
 }
 
 type registerPoRWorkflowInput struct {
@@ -246,7 +244,7 @@ type registerPoRWorkflowInput struct {
 	sethClient                  *seth.Client
 	deployerPrivateKey          string
 	blockchain                  *blockchain.Output
-	binaryDownloadOutput        binaryDownloadOutput
+	creCLIAbsPath               string
 }
 
 func registerPoRWorkflow(input registerPoRWorkflowInput) error {
@@ -284,7 +282,7 @@ func registerPoRWorkflow(input registerPoRWorkflowInput) error {
 
 	// compile and upload the workflow, if we are not using an existing one
 	if input.WorkflowConfig.ShouldCompileNewWorkflow {
-		compilationResult, err := libcrecli.CompileWorkflow(input.binaryDownloadOutput.creCLIAbsPath, *input.WorkflowConfig.WorkflowFolderLocation, workflowConfigFile, settingsFile)
+		compilationResult, err := libcrecli.CompileWorkflow(input.creCLIAbsPath, *input.WorkflowConfig.WorkflowFolderLocation, workflowConfigFile, settingsFile)
 		if err != nil {
 			return errors.Wrap(err, "failed to compile workflow")
 		}
@@ -296,7 +294,7 @@ func registerPoRWorkflow(input registerPoRWorkflowInput) error {
 		workflowConfigURL = input.WorkflowConfig.CompiledWorkflowConfig.ConfigURL
 	}
 
-	registerErr := libcrecli.DeployWorkflow(input.binaryDownloadOutput.creCLIAbsPath, input.WorkflowName, workflowURL, workflowConfigURL, settingsFile)
+	registerErr := libcrecli.DeployWorkflow(input.creCLIAbsPath, input.WorkflowName, workflowURL, workflowConfigURL, settingsFile)
 	if registerErr != nil {
 		return errors.Wrap(registerErr, "failed to register workflow")
 	}
@@ -451,7 +449,7 @@ type setupOutput struct {
 	nodeOutput           []*keystonetypes.WrappedNodeOutput
 }
 
-func setupTestEnvironment(t *testing.T, testLogger zerolog.Logger, in *TestConfig, priceProvider PriceProvider, binaryDownloadOutput binaryDownloadOutput, mustSetCapabilitiesFn func(input []*ns.Input) []*keystonetypes.CapabilitiesAwareNodeSet) *setupOutput {
+func setupTestEnvironment(t *testing.T, testLogger zerolog.Logger, in *TestConfig, priceProvider PriceProvider, mustSetCapabilitiesFn func(input []*ns.Input) []*keystonetypes.CapabilitiesAwareNodeSet) *setupOutput {
 	// Universal setup -- START
 
 	nodeSetInput := mustSetCapabilitiesFn(in.NodeSets)
@@ -615,6 +613,28 @@ func setupTestEnvironment(t *testing.T, testLogger zerolog.Logger, in *TestConfi
 			nodeSetInput[i].NodeSpecs[j].Node.TestConfigOverrides = config[j]
 			nodeSetInput[i].NodeSpecs[j].Node.TestSecretsOverrides = secrets[j]
 		}
+
+		// instruct Docker which capabilities to copy to the container
+		// TODO: add similar support for CRIB
+		if in.Infra.InfraType == libtypes.Docker {
+			if flags.HasFlag(donMetadata.Flags, keystonetypes.CronCapability) {
+				workerNodes, wErr := libnode.FindManyWithLabel(donMetadata.NodesMetadata, &keystonetypes.Label{
+					Key:   libnode.NodeTypeKey,
+					Value: keystonetypes.WorkerNode,
+				}, libnode.EqualLabels)
+				require.NoError(t, wErr, "failed to find worker nodes")
+
+				for _, node := range workerNodes {
+					nodeIndexStr, nErr := libnode.FindLabelValue(node, libnode.IndexKey)
+					require.NoError(t, nErr, "failed to find index label")
+
+					nodeIndex, nIErr := strconv.Atoi(nodeIndexStr)
+					require.NoError(t, nIErr, "failed to convert index to int")
+
+					nodeSetInput[i].NodeSpecs[nodeIndex].Node.CapabilitiesBinaryPaths = append(nodeSetInput[i].NodeSpecs[nodeIndex].Node.CapabilitiesBinaryPaths, in.WorkflowConfig.DependenciesConfig.CronCapabilityBinaryPath)
+				}
+			}
+		}
 	}
 
 	// Deploy the DONs
@@ -780,7 +800,7 @@ func setupTestEnvironment(t *testing.T, testLogger zerolog.Logger, in *TestConfi
 		sethClient:                  blockchainsOutput.sethClient,
 		deployerPrivateKey:          blockchainsOutput.deployerPrivateKey,
 		blockchain:                  blockchainsOutput.blockchainOutput,
-		binaryDownloadOutput:        binaryDownloadOutput,
+		creCLIAbsPath:               in.WorkflowConfig.DependenciesConfig.CRECLIBinaryPath,
 	}
 
 	err = registerPoRWorkflow(registerInput)
@@ -813,8 +833,10 @@ func TestCRE_OCR3_PoR_Workflow_SingleDon_MockedPrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 1, "expected 1 node set in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
+		downloadErr := downloadBinaryFiles(in)
+		require.NoError(t, downloadErr, "failed to download binary files")
+	}
 
 	// Assign all capabilities to the single node set
 	mustSetCapabilitiesFn := func(input []*ns.Input) []*keystonetypes.CapabilitiesAwareNodeSet {
@@ -832,7 +854,7 @@ func TestCRE_OCR3_PoR_Workflow_SingleDon_MockedPrice(t *testing.T) {
 	priceProvider, priceErr := NewFakePriceProvider(testLogger, in.Fake)
 	require.NoError(t, priceErr, "failed to create fake price provider")
 
-	setupOutput := setupTestEnvironment(t, testLogger, in, priceProvider, *binaryDownloadOutput, mustSetCapabilitiesFn)
+	setupOutput := setupTestEnvironment(t, testLogger, in, priceProvider, mustSetCapabilitiesFn)
 
 	// Log extra information that might help debugging
 	t.Cleanup(func() {
@@ -918,8 +940,10 @@ func TestCRE_OCR3_PoR_Workflow_GatewayDon_MockedPrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 2, "expected 2 node sets in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
+		downloadErr := downloadBinaryFiles(in)
+		require.NoError(t, downloadErr, "failed to download binary files")
+	}
 
 	// Assign all capabilities to the single node set
 	mustSetCapabilitiesFn := func(input []*ns.Input) []*keystonetypes.CapabilitiesAwareNodeSet {
@@ -943,7 +967,7 @@ func TestCRE_OCR3_PoR_Workflow_GatewayDon_MockedPrice(t *testing.T) {
 	priceProvider, priceErr := NewFakePriceProvider(testLogger, in.Fake)
 	require.NoError(t, priceErr, "failed to create fake price provider")
 
-	setupOutput := setupTestEnvironment(t, testLogger, in, priceProvider, *binaryDownloadOutput, mustSetCapabilitiesFn)
+	setupOutput := setupTestEnvironment(t, testLogger, in, priceProvider, mustSetCapabilitiesFn)
 
 	// Log extra information that might help debugging
 	t.Cleanup(func() {
@@ -1029,8 +1053,10 @@ func TestCRE_OCR3_PoR_Workflow_CapabilitiesDons_LivePrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 3, "expected 3 node sets in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
+		downloadErr := downloadBinaryFiles(in)
+		require.NoError(t, downloadErr, "failed to download binary files")
+	}
 
 	mustSetCapabilitiesFn := func(input []*ns.Input) []*keystonetypes.CapabilitiesAwareNodeSet {
 		return []*keystonetypes.CapabilitiesAwareNodeSet{
@@ -1057,7 +1083,7 @@ func TestCRE_OCR3_PoR_Workflow_CapabilitiesDons_LivePrice(t *testing.T) {
 	}
 
 	priceProvider := NewTrueUSDPriceProvider(testLogger)
-	setupOutput := setupTestEnvironment(t, testLogger, in, priceProvider, *binaryDownloadOutput, mustSetCapabilitiesFn)
+	setupOutput := setupTestEnvironment(t, testLogger, in, priceProvider, mustSetCapabilitiesFn)
 
 	// Log extra information that might help debugging
 	t.Cleanup(func() {


Reviews:
user: login: jmank88
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:43:08Z
user: login: skudasov
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-25T13:45:14Z
user: login: Tofel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:45:31Z
user: login: Tofel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:45:34Z

Review comments:
diff_hunk: @@ -1029,8 +1053,10 @@ func TestCRE_OCR3_PoR_Workflow_CapabilitiesDons_LivePrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 3, "expected 3 node sets in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
path: system-tests/tests/smoke/cre/por_test.go
user: login: skudasov
body: Do we set this flag in CI or is it a default GHA flag? @Tofel 
created_at: 2025-03-25T13:32:48Z
author_association: COLLABORATOR
diff_hunk: @@ -918,8 +940,10 @@ func TestCRE_OCR3_PoR_Workflow_GatewayDon_MockedPrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 2, "expected 2 node sets in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
path: system-tests/tests/smoke/cre/por_test.go
user: login: skudasov
body: Do we set this flag in CI or is it a default GHA flag? @Tofel
created_at: 2025-03-25T13:33:04Z
author_association: COLLABORATOR
diff_hunk: @@ -813,8 +833,10 @@ func TestCRE_OCR3_PoR_Workflow_SingleDon_MockedPrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 1, "expected 1 node set in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
path: system-tests/tests/smoke/cre/por_test.go
user: login: skudasov
body: Do we set this flag in CI or is it a default GHA flag? @Tofel
created_at: 2025-03-25T13:33:19Z
author_association: COLLABORATOR
diff_hunk: @@ -918,8 +940,10 @@ func TestCRE_OCR3_PoR_Workflow_GatewayDon_MockedPrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 2, "expected 2 node sets in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
path: system-tests/tests/smoke/cre/por_test.go
user: login: Tofel
body: we set it
created_at: 2025-03-25T13:45:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -813,8 +833,10 @@ func TestCRE_OCR3_PoR_Workflow_SingleDon_MockedPrice(t *testing.T) {
 	validateEnvVars(t, in)
 	require.Len(t, in.NodeSets, 1, "expected 1 node set in the test config")
 
-	binaryDownloadOutput, err := downloadBinaryFiles(in)
-	require.NoError(t, err, "failed to download binary files")
+	if os.Getenv("CI") == "true" {
path: system-tests/tests/smoke/cre/por_test.go
user: login: Tofel
body: we set it
created_at: 2025-03-25T13:45:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: cl-sonarqube-production[bot]
created_at: 2025-03-25T13:10:22Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16930) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16930&resolved=false) [0 New issues](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16930&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/fixed-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&fixedInPullRequest=16930) [0 Fixed issues](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&fixedInPullRequest=16930)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16930&issueStatuses=ACCEPTED) [0 Accepted issues](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16930&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/security_hotspots?id=smartcontractkit_chainlink&pullRequest=16930) [0 Security Hotspots](https://sonarqube.main.prod.cldev.sh/security_hotspots?id=smartcontractkit_chainlink&pullRequest=16930)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16930&metric=new_coverage&view=list) [0.0% Coverage on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16930&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16930&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16930&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16930)


author_association: NONE