Repository: saucelabs/node-saucelabs
Pull request #259 (2024-08-14 20:41:55)

Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/micromatch/braces/commit/74b2db2938fad48a2ea54a9c8bf27a37a62c350d"><code>74b2db2</code></a> 3.0.3</li>
<li><a href="https://github.com/micromatch/braces/commit/88f1429a0f47e1dd3813de35211fc97ffda27f9e"><code>88f1429</code></a> update eslint. lint, fix unit tests.</li>
<li><a href="https://github.com/micromatch/braces/commit/415d660c3002d1ab7e63dbf490c9851da80596ff"><code>415d660</code></a> Snyk js braces 6838727 (<a href="https://redirect.github.com/micromatch/braces/issues/40">#40</a>)</li>
<li><a href="https://github.com/micromatch/braces/commit/190510f79db1adf21d92798b0bb6fccc1f72c9d6"><code>190510f</code></a> fix tests, skip 1 test in test/braces.expand</li>
<li><a href="https://github.com/micromatch/braces/commit/716eb9f12d820b145a831ad678618731927e8856"><code>716eb9f</code></a> readme bump</li>
<li><a href="https://github.com/micromatch/braces/commit/a5851e57f45c3431a94d83fc565754bc10f5bbc3"><code>a5851e5</code></a> Merge pull request <a href="https://redirect.github.com/micromatch/braces/issues/37">#37</a> from coderaiser/fix/vulnerability</li>
<li><a href="https://github.com/micromatch/braces/commit/2092bd1fb108d2c59bd62e243b70ad98db961538"><code>2092bd1</code></a> feature: braces: add maxSymbols (<a href="https://github.com/micromatch/braces/issues/">https://github.com/micromatch/braces/issues/</a>...</li>
<li><a href="https://github.com/micromatch/braces/commit/9f5b4cf47329351bcb64287223ffb6ecc9a5e6d3"><code>9f5b4cf</code></a> fix: vulnerability (<a href="https://security.snyk.io/vuln/SNYK-JS-BRACES-6838727">https://security.snyk.io/vuln/SNYK-JS-BRACES-6838727</a>)</li>
<li><a href="https://github.com/micromatch/braces/commit/98414f9f1fabe021736e26836d8306d5de747e0d"><code>98414f9</code></a> remove funding file</li>
<li><a href="https://github.com/micromatch/braces/commit/665ab5d561c017a38ba7aafd92cc6655b91d8c14"><code>665ab5d</code></a> update keepEscaping doc (<a href="https://redirect.github.com/micromatch/braces/issues/27">#27</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/micromatch/braces/compare/3.0.2...3.0.3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=braces&package-manager=npm_and_yarn&previous-version=3.0.2&new-version=3.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/saucelabs/node-saucelabs/network/alerts).

</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.



Patch:
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index 9a6b946..6a243e7 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -52,7 +52,7 @@ jobs:
       - name: Install dependencies
         run: npm ci
       - name: Download built package
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4.1.7
         with:
           name: compiled-package
       - name: Release
diff --git a/README.md b/README.md
index 5f65ed0..e9f43aa 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # Node Sauce Labs [![Test Changes](https://github.com/saucelabs/node-saucelabs/actions/workflows/test.yml/badge.svg)](https://github.com/saucelabs/node-saucelabs/actions/workflows/test.yml)
 
-Wrapper around all Sauce Labs REST APIs for [Node.js](http://nodejs.org/) (v14 or higher) including support for
-[Sauce Connect Proxy](https://docs.saucelabs.com/secure-connections/sauce-connect/) and TypeScript definitions.
+Wrapper around all Sauce Labs REST APIs for [Node.js](http://nodejs.org/) (v18 or higher) including support for
+[Sauce Connect Proxy](https://docs.saucelabs.com/secure-connections/sauce-connect-5/) and TypeScript definitions.
 
 ## Install
 
@@ -63,7 +63,7 @@ This package if installed globally can be used as CLI tool to access the API fro
 ```sh
 $ npm install -g saucelabs
 ...
-$ sl listJobs $SAUCE_USERNAME --limit 5 --region eu
+$ sl listJobs $SAUCE_USERNAME 5 --region eu
 { jobs:
    [ { id: '19dab74f8fd848518f8d2c2cee3a6fbd' },
      { id: 'dc08ca0c7fa14eee909a093d11567328' },
@@ -104,12 +104,12 @@ or start Sauce Connect Proxy in EU datacenter:
 # start Sauce Connect tunnel for eu-central-1 region
 $ sl sc --region eu --tunnel-name "my-tunnel"
 # run a specific Sauce Connect version
-$ sl sc --scVersion 4.5.4
+$ sl sc --scVersion 5.2.2
 # see all available Sauce Connect parameters via:
 $ sl sc --help
 ```
 
-You can see all available Sauce Connect parameters on the [Sauce Labs Docs](https://docs.saucelabs.com/dev/cli/sauce-connect-proxy/).
+You can see all available Sauce Connect parameters on the [Sauce Labs Docs](https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/).
 
 ### As NPM Package
 
@@ -179,7 +179,7 @@ import SauceLabs from 'saucelabs';
      */
     logger: (stdout) => console.log(stdout),
     /**
-     * see all available parameters here: https://docs.saucelabs.com/dev/cli/sauce-connect-proxy/
+     * see all available parameters here: https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/
      * all parameters have to be applied camel cased instead of with hyphens, e.g.
      * to apply the `--tunnel-name` parameter, set:
      */
@@ -219,6 +219,7 @@ const myAccount = new SauceLabs({
   user: 'YOUR-USER',
   key: 'YOUR-ACCESS-KEY',
   region: 'eu', // run in EU datacenter
+  tunnelName: 'my-tunnel',
 });
 
 // get full webdriver url from the client depending on `region` option:
diff --git a/apis/sauce.json b/apis/sauce.json
index 063129c..dadb030 100644
--- a/apis/sauce.json
+++ b/apis/sauce.json
@@ -458,6 +458,36 @@
       },
       "type": "object"
     },
+    "SauceConnectDownload": {
+      "type": "object",
+      "properties": {
+        "download": {
+          "type": "object",
+          "properties": {
+            "url": {
+              "type": "string"
+            },
+            "version": {
+              "type": "string"
+            },
+            "checksums": {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "properties": {
+                  "value": {
+                    "type": "string"
+                  },
+                  "algorithm": {
+                    "type": "string"
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    },
     "SauceStatus": {
       "properties": {
         "service_operational": {
@@ -742,6 +772,27 @@
       "required": true,
       "type": "string"
     },
+    "clientArch": {
+      "description": "SC client host CPU arch",
+      "in": "query",
+      "name": "arch",
+      "required": false,
+      "type": "string"
+    },
+    "clientOS": {
+      "description": "SC client host OS",
+      "in": "query",
+      "name": "os",
+      "required": false,
+      "type": "string"
+    },
+    "clientVersion": {
+      "description": "SC client version",
+      "in": "query",
+      "name": "version",
+      "required": false,
+      "type": "string"
+    },
     "filepath": {
       "description": "file path to store the asset at",
       "in": "path",
@@ -756,6 +807,14 @@
       "required": true,
       "type": "string"
     },
+    "filter": {
+      "description": "Filter expression to apply before returning query results",
+      "in": "query",
+      "name": "filter",
+      "enum": ["v2alpha", "one_per_pool"],
+      "required": false,
+      "type": "string"
+    },
     "full": {
       "description": "Should the response result contain everything or just the basics",
       "in": "query",
@@ -784,72 +843,6 @@
       "name": "limit",
       "type": "number"
     },
-    "manual_capabilities": {
-      "description": "desired capabilities to start the session",
-      "in": "body",
-      "name": "capabilities",
-      "required": true,
-      "schema": {
-        "properties": {
-          "assets": {
-            "description": "if true it stores assets of the manual job",
-            "type": "boolean"
-          },
-          "browser": {
-            "description": "system browser name (e.g. \"chrome\")",
-            "type": "string"
-          },
-          "browserDisplay": {
-            "description": "sanitized browser name displayed in the UI (e.g. \"Google Chrome\")",
-            "type": "string"
-          },
-          "browserVersionDisplay": {
-            "description": "sanitized browser version (e.g. \"53\")",
-            "type": "string"
-          },
-          "device": {
-            "description": "device name if manual testing is on mobile",
-            "type": "string"
-          },
-          "os": {
-            "description": "system OS name (e.g. \"Windows 2012 R2\")",
-            "type": "string"
-          },
-          "osDisplay": {
-            "description": "sanitized OS name (e.g. \"Windows\")",
-            "type": "string"
-          },
-          "res": {
-            "description": "browser resolution (e.g. \"1400x1050\")",
-            "type": "string"
-          },
-          "tunnel": {
-            "description": "tunnel id to run manual test on (if available)",
-            "type": "string"
-          },
-          "url": {
-            "description": "url to navigate to",
-            "type": "string"
-          },
-          "version": {
-            "description": "system browser version",
-            "type": "string"
-          }
-        },
-        "required": [
-          "assets",
-          "browser",
-          "browserDisplay",
-          "version",
-          "browserVersionDisplay",
-          "os",
-          "osDisplay",
-          "res",
-          "url"
-        ],
-        "type": "object"
-      }
-    },
     "metric": {
       "description": "name of the category",
       "in": "body",
@@ -877,6 +870,21 @@
         "type": "object"
       }
     },
+    "protocol": {
+      "description": "Sauce Connect Protocol",
+      "in": "query",
+      "name": "protocol",
+      "required": false,
+      "enum": ["kgp", "h2c"],
+      "type": "string"
+    },
+    "reason": {
+      "description": "Reason for stopping a tunnel",
+      "in": "query",
+      "name": "reason",
+      "required": true,
+      "type": "string"
+    },
     "subaccounts": {
       "default": false,
       "description": "Include subaccounts in list of jobs",
@@ -884,13 +892,6 @@
       "name": "subaccounts",
       "type": "boolean"
     },
-    "taskId": {
-      "description": "task id of manual task",
-      "in": "path",
-      "name": "taskId",
-      "required": true,
-      "type": "string"
-    },
     "username": {
       "description": "username",
       "in": "path",
@@ -898,6 +899,13 @@
       "required": true,
       "type": "string"
     },
+    "wait_for_jobs": {
+      "default": true,
+      "description": "Wait for jobs to finish",
+      "in": "query",
+      "name": "wait_for_jobs",
+      "type": "boolean"
+    },
     "jobIds": {
       "description": "list of jobIds",
       "in": "query",
@@ -1038,15 +1046,16 @@
             "$ref": "#/parameters/full"
           },
           {
-            "default": false,
+            "required": false,
             "description": "Only return manual jobs",
             "in": "query",
             "name": "manual_only",
             "type": "boolean"
           },
           {
-            "default": false,
+            "required": false,
             "in": "query",
+            "description": "Only return automated jobs",
             "name": "auto_only",
             "type": "boolean"
           },
@@ -1219,256 +1228,6 @@
         "tags": ["Info"]
       }
     },
-    "/v1/manual": {
-      "delete": {
-        "description": "complete manual task",
-        "operationId": "delete_manual_job",
-        "parameters": [
-          {
-            "description": "list of task ids that to complete",
-            "in": "body",
-            "name": "ids",
-            "required": true,
-            "schema": {
-              "items": {
-                "type": "string"
-              },
-              "type": "array"
-            }
-          }
-        ],
-        "produces": ["text/plain"],
-        "responses": {
-          "200": {
-            "description": "no response payload",
-            "schema": {
-              "type": "null"
-            }
-          }
-        },
-        "summary": "complete manual task",
-        "tags": ["Manual"]
-      },
-      "post": {
-        "description": "Creates a manual job",
-        "operationId": "create_manual_job",
-        "parameters": [
-          {
-            "$ref": "#/parameters/manual_capabilities"
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "manual job was created successfully",
-            "schema": {
-              "description": "desired capabilities of manual job",
-              "properties": {
-                "error": {
-                  "description": "false if job got created successfully",
-                  "type": "boolean"
-                },
-                "job_id": {
-                  "description": "id of the job (available when created, otherwise \"null\")"
-                },
-                "metadata": {
-                  "description": "capabilities of the manual job",
-                  "properties": {
-                    "browser": {
-                      "description": "system browser name (e.g. \"chrome\")",
-                      "type": "string"
-                    },
-                    "os": {
-                      "description": "system OS name (e.g. \"Windows 2012 R2\")",
-                      "type": "string"
-                    },
-                    "resolution": {
-                      "description": "browser resolution (e.g. \"1400x1050\")",
-                      "type": "string"
-                    },
-                    "tunnel": {
-                      "description": "tunnel id to run manual test on (if available)"
-                    },
-                    "url": {
-                      "description": "start url of manual job",
-                      "type": "string"
-                    },
-                    "version": {
-                      "description": "system browser version",
-                      "type": "string"
-                    }
-                  },
-                  "required": ["url", "browser", "os", "version", "resolution"],
-                  "type": "object"
-                },
-                "task_id": {
-                  "description": "id of the manual task",
-                  "type": "string"
-                }
-              },
-              "required": ["task_id", "metadata"],
-              "type": "object"
-            }
-          },
-          "default": {
-            "description": "Unexpected error",
-            "schema": {
-              "$ref": "#/definitions/Error"
-            }
-          }
-        },
-        "summary": "Manual job creation",
-        "tags": ["Manual"]
-      }
-    },
-    "/v1/manual/options/": {
-      "get": {
-        "description": "returns a list of supported platforms in the Sauce cloud",
-        "operationId": "list_manual_platforms",
-        "responses": {
-          "200": {
-            "description": "object with list of supported platforms",
-            "schema": {
-              "additionalProperties": {
-                "additionalProperties": {
-                  "description": "operating system",
-                  "properties": {
-                    "browser": {
-                      "type": "string"
-                    },
-                    "platform": {
-                      "type": "string"
-                    },
-                    "resolutions": {
-                      "items": {
-                        "type": "string"
-                      },
-                      "type": "array"
-                    },
-                    "version": {
-                      "type": "string"
-                    }
-                  },
-                  "type": "object"
-                },
-                "description": "browser version",
-                "type": "object"
-              },
-              "description": "browser type (firefox, opera, chrome etc.)",
-              "type": "object"
-            }
-          },
-          "default": {
-            "description": "Unexpected error",
-            "schema": {
-              "$ref": "#/definitions/Error"
-            }
-          }
-        },
-        "summary": "Platform list with options for desired capabilities",
-        "tags": ["Manual", "Platform"]
-      }
-    },
-    "/v1/manual/{taskId}": {
-      "get": {
-        "description": "get manual task",
-        "operationId": "get_manual_job",
-        "parameters": [
-          {
-            "$ref": "#/parameters/taskId"
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "returns manual task",
-            "schema": {
-              "description": "desired capabilities of manual job",
-              "properties": {
-                "browser": {
-                  "description": "system browser name (e.g. \"chrome\")",
-                  "type": "string"
-                },
-                "members": {
-                  "items": {
-                    "type": "string"
-                  },
-                  "type": "array"
-                },
-                "os": {
-                  "description": "system OS name (e.g. \"Windows 2012 R2\")",
-                  "type": "string"
-                },
-                "owner": {
-                  "description": "owner of manual job",
-                  "type": "string"
-                },
-                "resolution": {
-                  "description": "browser resolution (e.g. \"1400x1050\")",
-                  "type": "string"
-                },
-                "tunnel": {
-                  "description": "tunnel id to run manual test on (if available)"
-                },
-                "url": {
-                  "description": "start url of manual job",
-                  "type": "string"
-                },
-                "version": {
-                  "description": "system browser version",
-                  "type": "string"
-                }
-              },
-              "required": [
-                "owner",
-                "url",
-                "browser",
-                "os",
-                "version",
-                "resolution",
-                "members"
-              ],
-              "type": "object"
-            }
-          },
-          "default": {
-            "description": "Unexpected error",
-            "schema": {
-              "$ref": "#/definitions/Error"
-            }
-          }
-        },
-        "summary": "get manual task",
-        "tags": ["Manual"]
-      }
-    },
-    "/v1/manual/{taskId}/screenshot": {
-      "post": {
-        "consumes": ["text/plain; charset=utf-8", "application/octet-stream"],
-        "description": "Take screenshot in manual session",
-        "operationId": "create_manual_job_screenshot",
-        "parameters": [
-          {
-            "$ref": "#/parameters/taskId"
-          }
-        ],
-        "produces": ["text/plain"],
-        "responses": {
-          "201": {
-            "description": "screenshot successfully taken",
-            "schema": {
-              "type": "null"
-            }
-          },
-          "default": {
-            "description": "Unexpected error",
-            "schema": {
-              "$ref": "#/definitions/Error"
-            }
-          }
-        },
-        "summary": "Take screenshot in manual session",
-        "tags": ["Manual"]
-      }
-    },
     "/v1/me": {
       "get": {
         "operationId": "get_current_user",
@@ -1506,107 +1265,6 @@
         "summary": "Authenticated user cookie information"
       }
     },
-    "/v1/tasks": {
-      "delete": {
-        "description": "complete manual task",
-        "operationId": "delete_manual_job_legacy",
-        "parameters": [
-          {
-            "description": "list of task ids that to complete",
-            "in": "body",
-            "name": "ids",
-            "required": true,
-            "schema": {
-              "items": {
-                "type": "string"
-              },
-              "type": "array"
-            }
-          }
-        ],
-        "produces": ["text/plain"],
-        "responses": {
-          "200": {
-            "description": "no response payload",
-            "schema": {
-              "type": "null"
-            }
-          }
-        },
-        "summary": "complete manual task",
-        "tags": ["Manual"]
-      },
-      "post": {
-        "description": "Creates a manual job",
-        "operationId": "create_manual_job_legacy",
-        "parameters": [
-          {
-            "$ref": "#/parameters/manual_capabilities"
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "manual job was created successfully",
-            "schema": {
-              "description": "desired capabilities of manual job",
-              "properties": {
-                "error": {
-                  "description": "false if job got created successfully",
-                  "type": "boolean"
-                },
-                "job_id": {
-                  "description": "id of the job (available when created, otherwise \"null\")"
-                },
-                "metadata": {
-                  "description": "capabilities of the manual job",
-                  "properties": {
-                    "browser": {
-                      "description": "system browser name (e.g. \"chrome\")",
-                      "type": "string"
-                    },
-                    "os": {
-                      "description": "system OS name (e.g. \"Windows 2012 R2\")",
-                      "type": "string"
-                    },
-                    "resolution": {
-                      "description": "browser resolution (e.g. \"1400x1050\")",
-                      "type": "string"
-                    },
-                    "tunnel": {
-                      "description": "tunnel id to run manual test on (if available)"
-                    },
-                    "url": {
-                      "description": "start url of manual job",
-                      "type": "string"
-                    },
-                    "version": {
-                      "description": "system browser version",
-                      "type": "string"
-                    }
-                  },
-                  "required": ["url", "browser", "os", "version", "resolution"],
-                  "type": "object"
-                },
-                "task_id": {
-                  "description": "id of the manual task",
-                  "type": "string"
-                }
-              },
-              "required": ["task_id", "metadata"],
-              "type": "object"
-            }
-          },
-          "default": {
-            "description": "Unexpected error",
-            "schema": {
-              "$ref": "#/definitions/Error"
-            }
-          }
-        },
-        "summary": "Manual job creation",
-        "tags": ["Manual"]
-      }
-    },
     "/v1/users/{username}": {
       "get": {
         "operationId": "get_user",
@@ -1963,6 +1621,12 @@
           },
           {
             "$ref": "#/parameters/full"
+          },
+          {
+            "$ref": "#/parameters/filter"
+          },
+          {
+            "$ref": "#/parameters/protocol"
           }
         ],
         "responses": {
@@ -2001,6 +1665,12 @@
           },
           {
             "$ref": "#/parameters/id"
+          },
+          {
+            "$ref": "#/parameters/reason"
+          },
+          {
+            "$ref": "#/parameters/wait_for_jobs"
           }
         ],
         "responses": {
@@ -2057,6 +1727,38 @@
         "tags": ["Tunnel"]
       }
     },
+    "/v1/public/tunnels/sauce-connect/download": {
+      "get": {
+        "description": "Get Sauce Connect download information for the latest version",
+        "operationId": "sc_download",
+        "parameters": [
+          {
+            "$ref": "#/parameters/clientArch"
+          },
+          {
+            "$ref": "#/parameters/clientOS"
+          },
+          {
+            "$ref": "#/parameters/clientVersion"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "download",
+            "schema": {
+              "$ref": "#/definitions/SauceConnectDownload"
+            }
+          },
+          "default": {
+            "description": "Unexpected error",
+            "schema": {
+              "$ref": "#/definitions/Error"
+            }
+          }
+        },
+        "tags": ["Tunnel"]
+      }
+    },
     "/v1/jobs/{id}/{assetName}": {
       "get": {
         "operationId": "download_job_asset",
diff --git a/docs/interface.md b/docs/interface.md
index e99ba6c..d6990fa 100644
--- a/docs/interface.md
+++ b/docs/interface.md
@@ -35,7 +35,7 @@ The following commands are available via package or cli tool:
         <h3>Example:</h3>
         <code>api.listJobs(username, { ...options })</code>
         <br><h4>Options</h4>
-        <ul>          <li><b>limit</b>: Number of results to return</li>          <li><b>subaccounts</b>: Include subaccounts in list of jobs</li>          <li><b>full</b>: Should the response result contain everything or just the basics</li>          <li><b>manual_only</b>: Only return manual jobs</li>          <li><b>auto_only</b>: No description available.</li>          <li><b>name</b>: name of the job</li>          <li><b>owner_type</b>: owner type for jobs</li>          <li><b>owner</b>: username of owner of the jobs</li>          <li><b>from</b>: receive jobs beginning of a specific timestamp</li>          <li><b>to</b>: receive jobs until specific timestamp</li>        </ul>      </td>
+        <ul>          <li><b>limit</b>: Number of results to return</li>          <li><b>subaccounts</b>: Include subaccounts in list of jobs</li>          <li><b>full</b>: Should the response result contain everything or just the basics</li>          <li><b>manual_only</b>: Only return manual jobs</li>          <li><b>auto_only</b>: Only return automated jobs</li>          <li><b>name</b>: name of the job</li>          <li><b>owner_type</b>: owner type for jobs</li>          <li><b>owner</b>: username of owner of the jobs</li>          <li><b>from</b>: receive jobs beginning of a specific timestamp</li>          <li><b>to</b>: receive jobs until specific timestamp</li>        </ul>      </td>
     </tr>
     <tr>
       <td>
@@ -61,46 +61,6 @@ The following commands are available via package or cli tool:
         <code>api.getStatus()</code>
       </td>
     </tr>
-    <tr>
-      <td>
-        <b>DELETE</b> <code>/v1/manual</code><br>
-        complete manual task
-        <h3>Example:</h3>
-        <code>api.deleteManualJob(ids)</code>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <b>POST</b> <code>/v1/manual</code><br>
-        Creates a manual job
-        <h3>Example:</h3>
-        <code>api.createManualJob(capabilities)</code>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <b>GET</b> <code>/v1/manual/options/</code><br>
-        returns a list of supported platforms in the Sauce cloud
-        <h3>Example:</h3>
-        <code>api.listManualPlatforms()</code>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <b>GET</b> <code>/v1/manual/{taskId}</code><br>
-        get manual task
-        <h3>Example:</h3>
-        <code>api.getManualJob(taskId)</code>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <b>POST</b> <code>/v1/manual/{taskId}/screenshot</code><br>
-        Take screenshot in manual session
-        <h3>Example:</h3>
-        <code>api.createManualJobScreenshot(taskId)</code>
-      </td>
-    </tr>
     <tr>
       <td>
         <b>GET</b> <code>/v1/me</code><br>
@@ -109,22 +69,6 @@ The following commands are available via package or cli tool:
         <code>api.getCurrentUser()</code>
       </td>
     </tr>
-    <tr>
-      <td>
-        <b>DELETE</b> <code>/v1/tasks</code><br>
-        complete manual task
-        <h3>Example:</h3>
-        <code>api.deleteManualJobLegacy(ids)</code>
-      </td>
-    </tr>
-    <tr>
-      <td>
-        <b>POST</b> <code>/v1/tasks</code><br>
-        Creates a manual job
-        <h3>Example:</h3>
-        <code>api.createManualJobLegacy(capabilities)</code>
-      </td>
-    </tr>
     <tr>
       <td>
         <b>GET</b> <code>/v1/users/{username}</code><br>
@@ -220,15 +164,16 @@ The following commands are available via package or cli tool:
         <h3>Example:</h3>
         <code>api.listTunnels(username, { ...options })</code>
         <br><h4>Options</h4>
-        <ul>          <li><b>all</b>: Should the response contain the same team user data</li>          <li><b>full</b>: Should the response result contain everything or just the basics</li>        </ul>      </td>
+        <ul>          <li><b>all</b>: Should the response contain the same team user data</li>          <li><b>full</b>: Should the response result contain everything or just the basics</li>          <li><b>filter</b>: Filter expression to apply before returning query results</li>          <li><b>protocol</b>: Sauce Connect Protocol</li>        </ul>      </td>
     </tr>
     <tr>
       <td>
         <b>DELETE</b> <code>/v1/{username}/tunnels/{id}</code><br>
         Delete a Tunnel
         <h3>Example:</h3>
-        <code>api.deleteTunnel(username, id)</code>
-      </td>
+        <code>api.deleteTunnel(username, id, reason, { ...options })</code>
+        <br><h4>Options</h4>
+        <ul>          <li><b>wait_for_jobs</b>: Wait for jobs to finish</li>        </ul>      </td>
     </tr>
     <tr>
       <td>
@@ -238,6 +183,15 @@ The following commands are available via package or cli tool:
         <code>api.getTunnel(username, id)</code>
       </td>
     </tr>
+    <tr>
+      <td>
+        <b>GET</b> <code>/v1/public/tunnels/sauce-connect/download</code><br>
+        Get Sauce Connect download information for the latest version
+        <h3>Example:</h3>
+        <code>api.scDownload({ ...options })</code>
+        <br><h4>Options</h4>
+        <ul>          <li><b>arch</b>: SC client host CPU architecture</li>          <li><b>os</b>: SC client host OS</li>        <li><b>version</b>: Optional. Return the newest version matching this minimal version</li>        </ul>      </td>
+    </tr>
     <tr>
       <td>
         <b>GET</b> <code>/v1/jobs/{id}/{assetName}</code><br>
@@ -256,15 +210,6 @@ The following commands are available via package or cli tool:
         <br><h4>Options</h4>
         <ul>          <li><b>full</b>: Should the response result contain everything or just the basics</li>        </ul>      </td>
     </tr>
-    <tr>
-      <td>
-        <b>POST</b> <code>/storage/upload</code><br>
-        Returns new application id after the upload.
-        <h3>Example:</h3>
-        <code>api.uploadApp({ ...options })</code>
-        <br><h4>Options</h4>
-        <ul>          <li><b>App-Type</b>: Application type</li>          <li><b>App-Identifier</b>: Your custom unique identifier for your app</li>          <li><b>App-DisplayName</b>: Your custom display name</li>          <li><b>App-Active</b>: If true makes uploaded application active one</li>          <li><b>body</b>: No description available.</li>        </ul>      </td>
-    </tr>
     <tr>
       <td>
         <b>GET</b> <code>/metrics/</code><br>
@@ -584,7 +529,7 @@ The following commands are available via package or cli tool:
         <h3>Example:</h3>
         <code>api.getBuildsV2(build_source, { ...options })</code>
         <br><h4>Options</h4>
-        <ul>          <li><b>user_id</b>: user_id</li>          <li><b>org_id</b>: org_id</li>          <li><b>group_id</b>: group_id</li>          <li><b>team_id</b>: team_id</li>          <li><b>status</b>: status</li>          <li><b>name</b>: start</li>          <li><b>end</b>: end</li>          <li><b>limit</b>: Number of results to return</li>          <li><b>offset</b>: Starting number</li>          <li><b>sort</b>: sort</li>        </ul>      </td>
+        <ul>          <li><b>user_id</b>: user_id</li>          <li><b>org_id</b>: org_id</li>          <li><b>group_id</b>: group_id</li>          <li><b>team_id</b>: team_id</li>          <li><b>status</b>: status</li>          <li><b>start_time</b>: start_time</li>          <li><b>end_time</b>: end_time</li>          <li><b>limit</b>: Number of results to return</li>          <li><b>offset</b>: Starting number</li>          <li><b>sort</b>: sort</li>        </ul>      </td>
     </tr>
     <tr>
       <td>
diff --git a/jest.config.js b/jest.config.js
index a8c6a4e..c35166d 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -5,8 +5,8 @@ module.exports = {
   collectCoverage: true,
   coverageThreshold: {
     global: {
-      branches: 93.5,
-      functions: 98,
+      branches: 93,
+      functions: 97,
       lines: 97,
       statements: 97,
     },
diff --git a/package-lock.json b/package-lock.json
index b26cc15..a4d4499 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "saucelabs",
-  "version": "8.0.0",
+  "version": "9.0.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "saucelabs",
-      "version": "8.0.0",
+      "version": "9.0.0",
       "license": "Apache-2.0",
       "dependencies": {
         "change-case": "^4.1.2",
@@ -4185,12 +4185,12 @@
       }
     },
     "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
@@ -6428,9 +6428,9 @@
       }
     },
     "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
       "dependencies": {
         "to-regex-range": "^5.0.1"
@@ -18074,12 +18074,12 @@
       }
     },
     "braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
       "requires": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       }
     },
     "browserslist": {
@@ -19747,9 +19747,9 @@
       }
     },
     "fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
       "requires": {
         "to-regex-range": "^5.0.1"
diff --git a/package.json b/package.json
index 07df263..b940193 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "saucelabs",
-  "version": "8.0.0",
+  "version": "9.0.0",
   "author": "Sauce Labs, Inc.",
   "description": "A wrapper around Sauce Labs REST API",
   "homepage": "https://github.com/saucelabs/node-saucelabs",
diff --git a/src/commands/sc.js b/src/commands/sc.js
index 0a8751b..cea9a22 100644
--- a/src/commands/sc.js
+++ b/src/commands/sc.js
@@ -2,7 +2,15 @@ import SauceLabs from './..';
 import {DEFAULT_OPTIONS, SAUCE_CONNECT_CLI_PARAMS} from '../constants';
 
 export const command = 'sc [flags]';
-export const describe = 'Sauce Connect interface';
+export const describe = `Sauce Connect Proxy interface.
+ - Only the 'sc run' command is currently supported
+ - See https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/ for detailed CLI documentation.
+ - Sauce Connect Proxy 4.x.x cannot be used with the library version 9.0.0 and newer
+ - Some Sauce Connect CLI option aliases differ from the 'sc' binary
+ - Some CLI options differ from the 'sc' binary:
+   - '--proxy' corresponds to https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/#proxy-sauce
+   - '--sc-upstream-proxy' corresponds to https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/#proxy
+`;
 export const builder = (yargs) => {
   for (const option of SAUCE_CONNECT_CLI_PARAMS) {
     yargs.option(option.name, option);
diff --git a/src/constants.js b/src/constants.js
index ce640c1..b3344c9 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -3,26 +3,9 @@ import os from 'os';
 
 import {version} from '../package.json';
 
-export const DEFAULT_SAUCE_CONNECT_VERSION = '4.9.1';
-export const SAUCE_CONNECT_BASE = 'https://saucelabs.com/downloads';
-export const SAUCE_CONNECT_VERSIONS_ENDPOINT =
-  'https://saucelabs.com/versions.json';
-export const SAUCE_CONNECT_PLATFORM_DATA = {
-  darwin: {
-    url: `${SAUCE_CONNECT_BASE}/sc-%s-osx.zip`,
-    use: 'bin/sc',
-  },
-  linux: {
-    url: `${SAUCE_CONNECT_BASE}/sc-%s-linux.tar.gz`,
-    use: 'bin/sc',
-  },
-  win32: {
-    url: `${SAUCE_CONNECT_BASE}/sc-%s-win32.zip`,
-    use: 'bin/sc',
-  },
-};
-
-export const SAUCE_VERSION_NOTE = `node-saucelabs v${version}\nSauce Connect v${DEFAULT_SAUCE_CONNECT_VERSION}`;
+export const DEFAULT_SAUCE_CONNECT_VERSION = '5';
+export const DEFAULT_RUNNER_NAME = 'node-saucelabs';
+export const SAUCE_VERSION_NOTE = `node-saucelabs v${version}\nSauce Connect v5 (latest)`;
 
 const protocols = [
   require('../apis/sauce.json'),
@@ -156,218 +139,178 @@ export const SAUCE_CONNECT_CLI_PARAMS = [
     description: 'Specify the Sauce Connect version you want to use.',
     default: DEFAULT_SAUCE_CONNECT_VERSION,
   },
-  {
-    /**
-     * Sauce Connect parameter
-     */
-    alias: 'a',
-    name: 'auth',
-    description:
-      'Perform basic authentication when an URL on <host:port> asks for a username and password.',
-  },
-  {
-    name: 'cainfo',
-    description:
-      'CA certificate bundle to use for verifying REST connections. (default "/usr/local/etc/openssl/cert.pem")',
-  },
-  {
-    name: 'capath',
-    description:
-      'Directory of CA certs to use for verifying REST connections. (default "/etc/ssl/certs")',
-    deprecated: true,
-  },
   {
     alias: 'c',
     name: 'config-file',
     description: 'Path to YAML config file.',
   },
   {
-    alias: 'D',
-    name: 'direct-domains',
+    alias: 'i',
+    name: 'tunnel-name',
     description:
-      'Comma-separated list of domains. Requests whose host matches one of these will be relayed directly through the internet, instead of through the tunnel.',
+      'Tunnel name used for this tunnel or the tunnels in the same HA pool.',
   },
   {
-    name: 'dns',
+    alias: 'M',
+    name: 'metadata',
     description:
-      'Use specified name server(s). Example: --dns 8.8.8.8,8.8.4.4:53',
+      'Custom metadata key-value pairs. This flag is, primarily, used by Sauce Labs to assign custom properties to the tunnel for reporting purposes.',
   },
   {
-    name: 'doctor',
+    alias: 's',
+    name: 'shared',
     description:
-      'Perform checks to detect possible misconfiguration or problems.',
+      "Share the tunnel within the same org unit. Only the 'all' option is currently supported. See here: https://docs.saucelabs.com/basics/acct-team-mgmt/sauce-connect-proxy-tunnels/.",
+  },
+  {
+    alias: 't',
+    name: 'tunnel-pool',
     type: 'boolean',
-    deprecated: true,
+    description:
+      'Denotes a tunnel as part of a high availability tunnel pool. See here: https://docs.saucelabs.com/secure-connections/sauce-connect/setup-configuration/high-availability/.',
   },
   {
     alias: 'F',
-    name: 'fast-fail-regexps',
+    name: 'deny-domains',
     description:
-      'Comma-separated list of regular expressions. Requests matching one of these will get dropped instantly and will not go through the tunnel.',
+      "Deny requests to the matching domains. Prefix domains with '-' to exclude requests from being denied. Special keyword 'all' matches all domains.",
   },
   {
-    alias: 'z',
-    name: 'log-stats',
-    description: 'Log statistics about HTTP traffic every <seconds>.',
-    type: 'number',
-    deprecated: true,
+    alias: 'D',
+    name: 'direct-domains',
+    description:
+      "Forward matching requests to their origin server over the public internet. Requests that don't match \"direct domains\" will be forwarded to customer-side over the Sauce Connect Proxy connection. You can specify --direct-domains or --tunnel-domains, but not both. Prefix domains with '-' to exclude requests from being forwarded directly.",
   },
   {
-    alias: 'l',
-    name: 'logfile',
-    description: 'Specify custom logfile.',
+    alias: 'B',
+    name: 'tls-passthrough-domains',
+    description:
+      "Pass matching requests to their origin server without SSL/TLS re-encryption. Requests that don't match will be re-encrypted. You can specify --tls-passthrough-domains or --tls-resign-domains, but not both. Prefix domains with '-' to exclude requests from being passed through.",
   },
   {
-    name: 'max-logsize',
+    alias: 'b',
+    name: 'tls-resign-domains',
     description:
-      'Rotate logfile after reaching <bytes> size. Disabled by default.',
-    type: 'number',
+      "Resign SSL/TLS certificates for matching requests. You can specify --tls-resign-domains or --tls-passthrough-domains, but not both. Prefix domains with '-' to exclude requests from being resigned.",
   },
   {
-    alias: 'M',
-    name: 'max-missed-acks',
+    alias: 'T',
+    name: 'tunnel-domains',
     description:
-      'The max number of keepalive acks that can be missed before triggering reconnect.',
-    type: 'number',
-    deprecated: true,
+      "Forward matching requests over the Sauce Connect Proxy connection. Requests not matching \"tunnel domains\" will be forwarded to their origin server over the public internet. You can specify --tunnel-domains or --direct-domains, but not both. Prefix domains with '-' to exclude requests from being forwarded over the SC Proxy connection. Special keyword 'all' matches all domains.",
   },
   {
-    name: 'metrics-address',
-    description: 'host:port server used to expose client-side metrics.',
-    deprecated: true,
+    name: 'tunnel-connections',
+    description:
+      'Number of connections to the Sauce Connect server. By default it is set to the number of CPUs on the machine. Total number of concurrent requests that can be handled is limited by the number of connections multiplied by the number of streams, see --tunnel-max-concurrent-streams flag. For example with 4 connections and 256 streams, the total number of concurrent requests is 1024.',
   },
   {
-    name: 'status-address',
-    description: 'host:port server used to expose client status.',
+    name: 'tunnel-max-concurrent-streams',
+    description:
+      'Maximal number of concurrent HTTP/2 streams per TCP connection.',
   },
   {
-    name: 'no-autodetect',
-    description: 'Disable the autodetection of proxy settings.',
-    type: 'boolean',
+    alias: 'a',
+    name: 'auth',
+    description: 'Site or upstream proxy basic authentication credentials.',
   },
   {
-    alias: 'N',
-    name: 'no-proxy-caching',
+    alias: 'H',
+    name: 'header',
     description:
-      'Disable caching in Sauce Connect. All requests will be sent through the tunnel.',
-    type: 'boolean',
-    deprecated: true,
+      'The header name will be normalized to canonical form. The header value should not contain any newlines or carriage returns. The flag can be specified multiple times. The following example removes the User-Agent header and all headers starting with X-.',
   },
   {
-    name: 'no-remove-colliding-tunnels',
+    name: 'pac',
     description:
-      "Don't remove identified tunnels with the same name, or any other default tunnels if this is a default tunnel. Jobs will be distributed between these tunnels, enabling load balancing and high availability. By default, colliding tunnels will be removed when Sauce Connect is starting up.",
-    type: 'boolean',
-    deprecated: true,
+      'Proxy Auto-Configuration file to use for upstream proxy selection.',
   },
   {
-    alias: 'B',
-    name: 'no-ssl-bump-domains',
+    name: 'sc-upstream-proxy',
     description:
-      'Comma-separated list of domains. Requests whose host matches one of these will not be SSL re-encrypted.',
+      'Upstream proxy for test sessions . It is used for requests received from the Sauce Connect Server only.',
   },
   {
-    name: 'pac',
+    name: 'proxy-localhost',
     description:
-      'Proxy autoconfiguration. Can be an http(s) or local file:// (absolute path only) URI.',
+      'Setting this to "allow" enables sending requests to localhost through the upstream proxy. Setting this to "direct" sends requests to localhost directly without using the upstream proxy. By default, requests to localhost are denied.',
   },
   {
-    alias: 'd',
-    name: 'pidfile',
-    description: 'File that will be created with the pid of the process.',
-  },
-  {
-    alias: 'T',
-    name: 'proxy-tunnel',
-    description: 'Use the proxy configured with -p for the tunnel connection.',
-    type: 'boolean',
+    name: 'proxy-sauce',
+    description:
+      'Establish a tunnel through an upstream proxy. Proxy for requests to Sauce Labs REST API and Sauce Connect servers only. See the -x, --proxy flag for more details on the format.',
   },
   {
-    alias: 'w',
-    name: 'proxy-userpwd',
+    name: 'dns-round-robin',
     description:
-      'Username and password required to access the proxy configured with -p.',
+      'If more than one DNS server is specified with the --dns-server flag, passing this flag will enable round-robin selection.',
   },
   {
-    alias: 'f',
-    name: 'readyfile',
-    description: 'File that will be touched to signal when tunnel is ready.',
+    alias: 'n',
+    name: 'dns-server',
+    description:
+      'DNS server(s) to use instead of system default. There are two execution policies, when more then one server is specified. Fallback: the first server in a list is used as primary, the rest are used as fallbacks. Round robin: the servers are used in a round-robin fashion. The port is optional, if not specified the default port is 53.',
   },
   {
-    alias: 'X',
-    name: 'scproxy-port',
-    description: 'Port on which scproxy will be listening.',
+    name: 'dns-timeout',
+    description:
+      'Timeout for dialing DNS servers. Only used if DNS servers are specified.',
   },
   {
-    name: 'scproxy-read-limit',
+    name: 'cacert-file',
     description:
-      'Rate limit reads in scproxy to X bytes per second. This option can be used to adjust local network transfer rate in order not to overload the tunnel connection.',
-    deprecated: true,
+      'Add your own CA certificates to verify against. The system root certificates will be used in addition to any certificates in this list. Use this flag multiple times to specify multiple CA certificate files.',
   },
   {
-    name: 'scproxy-write-limit',
+    name: 'http-dial-timeout',
     description:
-      'Rate limit writes in scproxy to X bytes per second. This option can be used to adjust local network transfer rate in order not to overload the tunnel connection.',
-    deprecated: true,
+      'The maximum amount of time a dial will wait for a connect to complete. With or without a timeout, the operating system may impose its own earlier timeout. For instance, TCP timeouts are often around 3 minutes.',
   },
   {
-    alias: 'P',
-    name: 'se-port',
+    name: 'http-idle-conn-timeout',
     description:
-      "Port on which Sauce Connect's Selenium relay will listen for requests. Selenium commands reaching Connect on this port will be relayed to Sauce Labs securely and reliably through Connect's tunnel (default 4445)",
-    type: 'number',
-    default: 4445,
+      'The maximum amount of time an idle (keep-alive) connection will remain idle before closing itself. Zero means no limit.',
   },
   {
-    alias: 's',
-    name: 'shared-tunnel',
+    name: 'http-response-header-timeout',
     description:
-      'Let sub-accounts of the tunnel owner use the tunnel if requested.',
-    type: 'boolean',
+      "The amount of time to wait for a server's response headers after fully writing the request (including its body, if any).This time does not include the time to read the response body. Zero means no limit.",
   },
   {
-    name: 'tunnel-cainfo',
+    name: 'http-tls-handshake-timeout',
     description:
-      'CA certificate bundle to use for verifying tunnel connections. (default "/usr/local/etc/openssl/cert.pem")',
+      'The maximum amount of time waiting to wait for a TLS handshake. Zero means no limit.',
   },
   {
-    name: 'tunnel-capath',
+    name: 'http-tls-keylog-file',
     description:
-      'Directory of CA certs to use for verifying tunnel connections. (default "/etc/ssl/certs")',
-    deprecated: true,
+      'File to log TLS master secrets in NSS key log format. By default, the value is taken from the SSLKEYLOGFILE environment variable. It can be used to allow external programs such as Wireshark to decrypt TLS connections.',
   },
   {
-    name: 'tunnel-cert',
+    name: 'api-address',
     description:
-      'Specify certificate to use for the tunnel connection, either public or private. Default: private. (default "private")',
+      'The server address to listen on. If the host is empty, the server will listen on all available interfaces.',
   },
   {
-    alias: 't',
-    name: 'tunnel-domains',
-    description:
-      "Inverse of '--direct-domains'. Only requests for domains in this list will be sent through the tunnel. Overrides '--direct-domains'.",
+    name: 'api-basic-auth',
+    description: 'Basic authentication credentials to protect the server.',
   },
   {
-    name: 'tunnel-identifier',
+    name: 'api-idle-timeout',
     description:
-      'Tunnel name used for this tunnel or the tunnels in the same HA pool.',
-    deprecated: true,
+      'The maximum amount of time to wait for the next request before closing connection.',
   },
   {
-    alias: 'i',
-    name: 'tunnel-name',
-    description:
-      'Tunnel name used for this tunnel or the tunnels in the same HA pool.',
+    name: 'log-file',
+    description: 'Path to the log file, if empty, logs to stdout.',
   },
   {
-    name: 'tunnel-pool',
-    description: 'The tunnel is a part of a high availability tunnel pool.',
+    name: 'log-http',
+    description: 'HTTP request and response logging mode.',
   },
   {
-    alias: 'v',
-    name: 'verbose',
-    type: 'boolean',
-    description: 'Enable verbose logging. Can be used up to two times.',
+    name: 'log-level',
+    description: 'Log level.',
   },
 ];
 export const SC_BOOLEAN_CLI_PARAMS = SAUCE_CONNECT_CLI_PARAMS.filter(
@@ -386,11 +329,6 @@ export const SC_PARAMS_TO_STRIP = [
   ...SAUCE_CONNECT_CLI_PARAM_ALIASES,
 ];
 
-export const SC_READY_MESSAGE = 'Sauce Connect is up, you may start your tests';
-export const SC_FAILURE_MESSAGES = [
-  'Sauce Connect could not establish a connection',
-  'Sauce Connect failed to start',
-];
-export const SC_WAIT_FOR_MESSAGES = ['\u001b[K', 'Please wait for']; // "\u001b" = Escape character
-export const SC_CLOSE_MESSAGE = 'Goodbye';
 export const SC_CLOSE_TIMEOUT = 5000;
+export const SC_READY_TIMEOUT = 30000;
+export const SC_HEALTHCHECK_TIMEOUT = 1000;
diff --git a/src/index.js b/src/index.js
index 3e2ffda..ea39251 100644
--- a/src/index.js
+++ b/src/index.js
@@ -7,9 +7,9 @@ import {spawn} from 'child_process';
 import got from 'got';
 import FormData from 'form-data';
 import {camelCase} from 'change-case';
-import queryString from 'query-string';
-
 import {
+  getCPUArch,
+  getPlatform,
   createHMAC,
   getAPIHost,
   getAssetHost,
@@ -20,6 +20,8 @@ import {
   getStrictSsl,
   getRegionSubDomain,
 } from './utils';
+import queryString from 'query-string';
+
 import {
   PROTOCOL_MAP,
   DEFAULT_OPTIONS,
@@ -28,16 +30,12 @@ import {
   SYMBOL_ITERATOR,
   TO_STRING_TAG,
   SC_PARAMS_TO_STRIP,
-  SC_READY_MESSAGE,
-  SC_CLOSE_MESSAGE,
-  SC_CLOSE_TIMEOUT,
   DEFAULT_SAUCE_CONNECT_VERSION,
-  SC_FAILURE_MESSAGES,
-  SAUCE_CONNECT_VERSIONS_ENDPOINT,
-  SC_WAIT_FOR_MESSAGES,
   SC_BOOLEAN_CLI_PARAMS,
+  DEFAULT_RUNNER_NAME,
 } from './constants';
 import SauceConnectLoader from './sauceConnectLoader';
+import {SauceConnectManager} from './sauceConnectManager';
 
 export default class SauceLabs {
   constructor(options) {
@@ -58,7 +56,8 @@ export default class SauceLabs {
     });
 
     if (typeof this._options.proxy === 'string') {
-      var proxyAgent = createProxyAgent(this._options.proxy);
+      this.proxy = this._options.proxy;
+      const proxyAgent = createProxyAgent(this.proxy);
       this._api = got.extend(
         {
           agent: proxyAgent,
@@ -240,33 +239,36 @@ export default class SauceLabs {
       }
     }
 
-    let sauceConnectVersion = argv.scVersion;
-    if (!sauceConnectVersion) {
-      sauceConnectVersion = await this._getLatestSauceConnectVersion();
+    const sauceConnectVersion = argv.scVersion || DEFAULT_SAUCE_CONNECT_VERSION;
+    if (sauceConnectVersion.startsWith('4')) {
+      throw new Error(
+        `This Sauce Connect version (${sauceConnectVersion}) is no longer supported. Please use Sauce Connect 5.`
+      );
     }
+
+    const scUpstreamProxy = argv.scUpstreamProxy;
     const args = Object.entries(argv)
       /**
        * filter out yargs, yargs params and custom parameters
        */
       .filter(
         ([k]) =>
-          !['_', '$0', 'sc-version', 'logger', ...SC_PARAMS_TO_STRIP].includes(
-            k
-          )
+          ![
+            '_',
+            '$0',
+            'api-address',
+            'metadata',
+            'sc-version',
+            'sc-upstream-proxy',
+            'tunnel-name',
+            'logger',
+            ...SC_PARAMS_TO_STRIP,
+          ].includes(k)
       )
       /**
        * remove duplicate params by yargs
        */
       .filter(([k]) => !k.match(/[A-Z]/g))
-      /**
-       * replace tunnel-identifier for tunnel-name
-       */
-      .map(([k, v]) => [k === 'tunnel-identifier' ? 'tunnel-name' : k, v])
-      /**
-       * SC uses `--no-XXX` params which gets parsed out by yargs
-       * therefor we need to re-add it here
-       */
-      .map(([k, v]) => [typeof v === 'boolean' && !v ? `no-${k}` : k, v])
       /**
        * SC doesn't like boolean values, so we need to make sure to
        * no pass it along when we deal with a boolean param
@@ -274,96 +276,122 @@ export default class SauceLabs {
       .map(([k, v]) =>
         SC_BOOLEAN_CLI_PARAMS.includes(k) ? `--${k}` : `--${k}=${v}`
       );
-    args.push(`--user=${this.username}`);
-    args.push(`--api-key=${this._accessKey}`);
+    args.push(`--username=${this.username}`);
+    args.push(`--access-key=${this._accessKey}`);
+
+    if (scUpstreamProxy) {
+      // map `--sc-upstream-proxy` to sc's `--proxy`. It's done because the app CLI
+      // conflicts with sc's CLI, `--proxy` here is equivalent to `--proxy-sauce` in sc.
+      // See: https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/#proxy
+      // See: https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/#proxy and
+      // https://docs.saucelabs.com/dev/cli/sauce-connect-5/run/#proxy-sauce
+      args.push(`--proxy=${scUpstreamProxy}`);
+    }
+    if (this.proxy) {
+      args.push(`--proxy-sauce=${this.proxy}`);
+    }
+
+    // Provide a default runner name. It's used for identifying the tunnel's initiation method.
+    let metadata = argv.metadata || '';
+    if (!metadata.includes('runner=')) {
+      metadata = [metadata, `runner=${DEFAULT_RUNNER_NAME}`]
+        .filter(Boolean)
+        .join(',');
+    }
+    args.push(`--metadata=${metadata}`);
+
+    const apiAddress = argv.apiAddress || ':8032';
+    args.push(`--api-address=${apiAddress}`);
 
     const region = argv.region || this.region;
     if (region) {
       const scRegion = getRegionSubDomain({region});
       args.push(`--region=${scRegion}`);
+    } else {
+      // --region is required for Sauce Connect 5.
+      throw new Error('Missing region');
     }
-    const scLoader = new SauceConnectLoader({sauceConnectVersion});
-    await scLoader.verifyAlreadyDownloaded();
-    const cp = spawn(scLoader.path, args);
-    return new Promise((resolve, reject) => {
-      const close = () =>
-        new Promise((resolveClose) => {
-          process.kill(cp.pid, 'SIGINT');
-          const timeout = setTimeout(resolveClose, SC_CLOSE_TIMEOUT);
-          cp.stdout.on('data', (data) => {
-            const output = data.toString();
-            if (output.includes(SC_CLOSE_MESSAGE)) {
-              clearTimeout(timeout);
-              return resolveClose(returnObj);
-            }
-          });
-        });
-      const returnObj = {cp, close};
-
-      cp.stderr.on('data', (data) => {
-        const output = data.toString();
-
-        /**
-         * check if error output is just an escape sequence or
-         * other expected data
-         */
-        if (
-          SC_WAIT_FOR_MESSAGES.find((msg) =>
-            escape(output).includes(escape(msg))
-          )
-        ) {
-          return;
-        }
 
-        return reject(new Error(output));
-      });
-      cp.stdout.on('data', (data) => {
-        const logger = fromCLI
-          ? process.stdout.write.bind(process.stdout)
-          : argv.logger;
-        const output = data.toString();
-        /**
-         * print to stdout if called via CLI
-         */
-        if (typeof logger === 'function') {
-          logger(output);
-        }
+    const tunnelName = argv.tunnelName;
+    if (tunnelName) {
+      args.push(`--tunnel-name=${tunnelName}`);
+    } else {
+      // --tunnel-name is required for Sauce Connect 5.
+      throw new Error('Missing tunnel-name');
+    }
 
-        /**
-         * fail if SauceConnect could not establish a connection
-         */
-        if (
-          SC_FAILURE_MESSAGES.find((msg) =>
-            escape(output).includes(escape(msg))
-          )
-        ) {
-          return reject(new Error(output));
-        }
+    // download and verify the Sauce Connect client
+    let scLoader = new SauceConnectLoader(sauceConnectVersion);
+    const isDownloaded = await scLoader.verifyAlreadyDownloaded();
+
+    if (!isDownloaded) {
+      console.info(`Downloading Sauce Connect v${sauceConnectVersion}...`);
+      let download = await this._getSauceConnectDownload(sauceConnectVersion);
+      // downloaded version may differ from the input version, eg. if a partial version is given as input
+      // update scLoader if necessary
+      if (download.version != sauceConnectVersion) {
+        scLoader = new SauceConnectLoader(download.version);
+      }
+      await scLoader.verifyAlreadyDownloaded({url: download.url});
+    }
 
-        /**
-         * continue if connection was established
-         */
-        if (output.includes(SC_READY_MESSAGE)) {
-          return resolve(returnObj);
-        }
-      });
+    if (args.length == 0 || args[0] != 'run') {
+      args.unshift('run');
+    }
 
-      process.on('SIGINT', close);
-      return returnObj;
-    });
+    const logger = fromCLI
+      ? process.stdout.write.bind(process.stdout)
+      : argv.logger;
+    const cp = spawn(scLoader.path, args);
+    const manager = new SauceConnectManager(cp, logger);
+    process.on('SIGINT', () => manager.close());
+
+    try {
+      await manager.waitForReady(apiAddress);
+      return {cp, close: () => manager.close()};
+    } catch (err) {
+      await manager.close();
+      throw err;
+    }
   }
 
-  async _getLatestSauceConnectVersion() {
+  /**
+   * Retrieve the download URL for the Sauce Connect client specific to this device's OS and architecture.
+   * Throws an exception on any error response
+   * @param {string} version Full or partial version for the download to match
+   * @returns {Object} download
+   * @returns {string} download.url
+   * @returns {string} download.version
+   * @returns {Object[]} download.checksums
+   * @returns {string} download.checksums[].value
+   * @returns {string} download.checksums[].algorithm
+   */
+  async _getSauceConnectDownload(version) {
+    const platform = getPlatform();
+    const cpuArch = getCPUArch();
+    var response = {};
     try {
-      const {body} = await this._api.get(SAUCE_CONNECT_VERSIONS_ENDPOINT, {
-        responseType: 'json',
+      response = await this._callAPI('scDownload', {
+        os: platform,
+        arch: cpuArch,
+        version: version,
       });
-      const responseJson = body.data;
-      return responseJson['Sauce Connect']['version'];
     } catch (err) {
-      // fallback
-      return DEFAULT_SAUCE_CONNECT_VERSION;
+      // if this endpoint is down, the start tunnels endpoint is likely down as well.
+      throw new Error(`Failed to retrieve Sauce Connect download. ${err}`);
+    }
+
+    if (response.error) {
+      // likely an input value error. some platform/arch combinations may not be supported.
+      throw new Error(
+        `Failed to retrieve Sauce Connect download. code: ${response.error.code} message: ${response.error.message}`
+      );
+    }
+    if (!response.download) {
+      // unexpected, inconsistent with API definition
+      throw new Error(`Failed to retrieve Sauce Connect download.`);
     }
+    return response.download;
   }
 
   async _downloadJobAsset(jobId, assetName, {filepath} = {}) {
@@ -529,20 +557,31 @@ export default class SauceLabs {
     const options = args.slice(pathParams.length)[0] || {};
     for (const optionParam of params.filter((p) => p.in === 'query')) {
       const expectedType = optionParam.type.replace('integer', 'number');
+      // support query options passed in camelCased form.
       const optionName = camelCase(optionParam.name);
-      const option = options[optionName];
+      let optionValueFromParams = options[optionName];
+      if (
+        optionParam.name !== optionName &&
+        typeof optionValueFromParams === 'undefined'
+      ) {
+        optionValueFromParams = options[optionParam.name];
+      }
+      const optionValue = optionValueFromParams;
       const isRequired =
         Boolean(optionParam.required) ||
         (typeof optionParam.required === 'undefined' &&
           typeof optionParam.default === 'undefined');
-      if ((isRequired || option) && !isValidType(option, expectedType)) {
+      if (
+        (isRequired || optionValue) &&
+        !isValidType(optionValue, expectedType)
+      ) {
         throw new Error(
-          `Expected parameter for option '${optionName}' from type '${expectedType}', found '${typeof option}'`
+          `Expected parameter for option '${optionName}' from type '${expectedType}', found '${typeof optionValue}'`
         );
       }
 
-      if (typeof option !== 'undefined') {
-        bodyMap.set(optionParam.name, option);
+      if (typeof optionValue !== 'undefined') {
+        bodyMap.set(optionParam.name, optionValue);
       }
     }
 
@@ -579,7 +618,7 @@ export default class SauceLabs {
     } catch (err) {
       throw new Error(
         `Failed calling ${propName}: ${err.message}, ${
-          err.response && err.response.body
+          err.response && JSON.stringify(err.response.body)
         }`
       );
     }
diff --git a/src/sauceConnectHealthcheck.js b/src/sauceConnectHealthcheck.js
new file mode 100644
index 0000000..0c1d79a
--- /dev/null
+++ b/src/sauceConnectHealthcheck.js
@@ -0,0 +1,12 @@
+import {SC_HEALTHCHECK_TIMEOUT} from './constants';
+
+export default class SauceConnectHealthCheck {
+  async perform(apiAddress) {
+    const response = await fetch(`${apiAddress}/readyz`, {
+      signal: AbortSignal.timeout(SC_HEALTHCHECK_TIMEOUT),
+    });
+    if (response.status !== 200) {
+      throw new Error(`response status code ${response.status} != 200`);
+    }
+  }
+}
diff --git a/src/sauceConnectLoader.js b/src/sauceConnectLoader.js
index debdd59..34380cc 100644
--- a/src/sauceConnectLoader.js
+++ b/src/sauceConnectLoader.js
@@ -20,9 +20,8 @@
  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
-import {format} from 'util';
-import {SAUCE_CONNECT_PLATFORM_DATA} from './constants';
-import {getPlatform} from './utils';
+// import SauceLabs from './';
+import {getCPUArch, getPlatform, isWindows} from './utils';
 import {mkdirSync, unlinkSync, createWriteStream} from 'fs';
 import {basename, join} from 'path';
 import https from 'https';
@@ -30,33 +29,33 @@ import fs from 'fs/promises';
 import compressing from 'compressing';
 
 export default class SauceConnectLoader {
-  constructor(options = {}) {
-    const platform = getPlatform();
-    const platformData = SAUCE_CONNECT_PLATFORM_DATA[platform];
-    if (!platformData) {
-      throw new ReferenceError(`Unsupported platform ${platform}`);
-    }
-    const {url, use} = platformData;
-    this.url = format(url, options.sauceConnectVersion);
+  constructor(version) {
     this.destDir = join(__dirname, 'sc-loader');
     this.destSC = join(
       __dirname,
       'sc-loader',
-      `.sc-v${options.sauceConnectVersion}`
+      `.sc-v${version}-${getPlatform()}-${getCPUArch()}`
     );
-    this.path = join(this.destSC, use);
+    let scBinary = 'sc';
+    if (isWindows()) {
+      scBinary += '.exe';
+    }
+    this.path = join(this.destSC, scBinary);
   }
 
   /**
-   * Verify if SC was already downloaded,
+   * Verify if SC was already downloaded.
    * if not then download it
    *
    * @api public
    */
-  verifyAlreadyDownloaded() {
+  verifyAlreadyDownloaded(options = {}) {
     return fs.stat(this.path).catch((err) => {
       if (err?.code === 'ENOENT') {
-        return this._download();
+        if (options.url) {
+          return this._download(options.url);
+        }
+        return false;
       }
       throw err;
     });
@@ -65,13 +64,13 @@ export default class SauceConnectLoader {
   /**
    * Download Sauce Connect
    */
-  _download() {
+  _download(sauceConnectURL) {
     mkdirSync(this.destDir, {recursive: true});
-    const compressedFilePath = join(this.destDir, basename(this.url));
+    const compressedFilePath = join(this.destDir, basename(sauceConnectURL));
     return new Promise((resolve, reject) => {
       const file = createWriteStream(compressedFilePath);
       https
-        .get(this.url, (response) => {
+        .get(sauceConnectURL, (response) => {
           response.pipe(file);
           file.on('finish', () => {
             file.close();
@@ -83,25 +82,18 @@ export default class SauceConnectLoader {
           reject(err);
         });
     }).then(() => {
-      if (getPlatform() === 'linux') {
+      if (compressedFilePath.endsWith('.tar.gz')) {
         return compressing.tgz
-          .uncompress(compressedFilePath, this.destDir, {
-            strip: 1,
-          })
+          .uncompress(compressedFilePath, this.destSC)
           .then(() => {
-            const extractedDir = compressedFilePath.replace('.tar.gz', '');
-            return fs.rename(extractedDir, this.destSC).then(() => {
-              // ensure the sc executable is actually executable
-              return fs.chmod(this.path, 0o755);
-            });
+            // ensure the sc executable is actually executable
+            return fs.chmod(this.path, 0o755);
           });
       } else {
         return compressing.zip
-          .uncompress(compressedFilePath, this.destSC, {
-            strip: 1,
-          })
+          .uncompress(compressedFilePath, this.destSC)
           .then(() => {
-            if (getPlatform() !== 'win32') {
+            if (!isWindows()) {
               // ensure the sc executable is actually executable
               return fs.chmod(this.path, 0o755);
             }
diff --git a/src/sauceConnectManager.js b/src/sauceConnectManager.js
new file mode 100644
index 0000000..f775f96
--- /dev/null
+++ b/src/sauceConnectManager.js
@@ -0,0 +1,94 @@
+import {
+  SC_CLOSE_TIMEOUT,
+  SC_HEALTHCHECK_TIMEOUT,
+  SC_READY_TIMEOUT,
+} from './constants';
+import SauceConnectHealthCheck from './sauceConnectHealthcheck';
+
+export class SauceConnectManager {
+  constructor(cp, logger) {
+    this.cp = cp;
+    this.logger = logger || (() => {});
+    this.healthcheck = new SauceConnectHealthCheck();
+    this._healthcheckInterval = null;
+    this._readyTimeout = null;
+  }
+
+  waitForReady(apiAddress) {
+    apiAddress = this._parseApiAddress(apiAddress);
+
+    return new Promise((resolve, reject) => {
+      this.cp.stderr.on('data', (data) => {
+        const output = data.toString();
+        return reject(new Error(output));
+      });
+
+      this.cp.stdout.on('data', (data) => {
+        const logger = this.logger;
+        if (typeof logger === 'function') {
+          logger(data.toString());
+        }
+      });
+
+      this._healthcheckInterval = setInterval(async () => {
+        if (this.cp.exitCode !== null && this.cp.exitCode !== undefined) {
+          clearInterval(this._healthcheckInterval);
+          clearTimeout(this._readyTimeout);
+          return reject(
+            new Error('Sauce Connect exited before reaching a ready state')
+          );
+        }
+
+        this.healthcheck
+          .perform(apiAddress)
+          .then(() => clearInterval(this._healthcheckInterval))
+          .then(() => clearTimeout(this._readyTimeout))
+          .then(resolve)
+          .catch(() => {});
+      }, SC_HEALTHCHECK_TIMEOUT);
+
+      this._readyTimeout = setTimeout(() => {
+        clearInterval(this._healthcheckInterval);
+        reject(new Error('Timeout waiting for healthcheck endpoint'));
+      }, SC_READY_TIMEOUT);
+    });
+  }
+
+  close() {
+    this.logger('Terminating Sauce Connect...');
+
+    return new Promise((resolve) => {
+      if (this.cp.exitCode !== null && this.cp.exitCode !== undefined) {
+        return resolve();
+      }
+
+      const timeout = setTimeout(() => {
+        this.logger('Forcefully terminating Sauce Connect...');
+        killSilently(this.cp.pid, 'SIGKILL');
+        resolve();
+      }, SC_CLOSE_TIMEOUT);
+
+      this.cp.on('exit', () => {
+        clearTimeout(timeout);
+        resolve();
+      });
+
+      clearInterval(this._healthcheckInterval);
+      clearTimeout(this._readyTimeout);
+      killSilently(this.cp.pid, 'SIGINT');
+    });
+  }
+
+  _parseApiAddress(value) {
+    const [host, port] = value.split(':');
+    return `http://${host || 'localhost'}:${port}`;
+  }
+}
+
+function killSilently(pid, signal) {
+  try {
+    process.kill(pid, signal);
+  } catch (err) {
+    // ignore
+  }
+}
diff --git a/src/utils.js b/src/utils.js
index 342492f..8cab378 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -192,8 +192,35 @@ export function getStrictSsl() {
 }
 
 /**
- * Mainly just here for testing
+ * Returns whether this platform is running a Windows OS
+ */
+export function isWindows() {
+  return process.platform.startsWith('win');
+}
+
+/**
+ * Returns an OS platform compatible with scDownload endpoint
+ * - macos
+ * - linux
+ * - windows
  */
 export function getPlatform() {
+  if (isWindows()) {
+    return 'windows';
+  } else if (process.platform == 'darwin') {
+    return 'macos';
+  }
   return process.platform;
 }
+
+/**
+ * Returns CPU architecture compatible with scDownload endpoint
+ * - 'x86_64'
+ * - 'arm64'
+ */
+export function getCPUArch() {
+  if (process.arch == 'x64') {
+    return 'x86_64';
+  }
+  return process.arch;
+}


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 64cc3fc..26620da 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,12 +11,12 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        node-version: [14.x, 16.x, 18.x]
+        node-version: [18.x, 20.x, 22.x]
     steps:
       - name: Clone Repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: Setup Node version
-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node-version }}
       - name: Install dependencies
diff --git a/e2e/jobs.test.js b/e2e/jobs.test.js
new file mode 100644
index 0000000..03ffabb
--- /dev/null
+++ b/e2e/jobs.test.js
@@ -0,0 +1,51 @@
+import SauceLabs from '../build';
+// import SauceLabs from 'saucelabs';
+
+// Only run the test when the env var is present
+// in GitHub Actions, otherwise it fails for untrusted PRs
+const SKIP_TEST = process.env.GITHUB_RUN_ID && !process.env.SAUCE_USERNAME;
+
+jest.setTimeout(60 * 1000); // 60s should be sufficient to boot SC
+
+/**
+ * unmock
+ */
+jest.unmock('https').unmock('got').unmock('yargs');
+
+test('should receive 400', async () => {
+  if (SKIP_TEST) {
+    return;
+  }
+  const api = new SauceLabs();
+  await expect(
+    api.listJobs(process.env.SAUCE_USERNAME, {
+      limit: 1,
+      autoOnly: true,
+      manualOnly: true,
+    })
+  ).rejects.toThrow(
+    'Failed calling listJobs: Response code 400 (Bad Request), {"message":"Invalid combination of arguments."}'
+  );
+});
+
+test('should be able to list automated jobs', async () => {
+  if (SKIP_TEST) {
+    return;
+  }
+  const api = new SauceLabs();
+  await api.listJobs(process.env.SAUCE_USERNAME, {
+    limit: 1,
+    autoOnly: true,
+  });
+});
+
+test('should be able to list live-testing jobs', async () => {
+  if (SKIP_TEST) {
+    return;
+  }
+  const api = new SauceLabs();
+  await api.listJobs(process.env.SAUCE_USERNAME, {
+    limit: 1,
+    manualOnly: true,
+  });
+});
diff --git a/e2e/sc.test.js b/e2e/sc.test.js
index 7ed7468..3b351e4 100644
--- a/e2e/sc.test.js
+++ b/e2e/sc.test.js
@@ -18,18 +18,49 @@ jest
   .unmock('compressing')
   .unmock('zlib');
 
+test('should be able to get Sauce Connect versions', async () => {
+  if (SKIP_TEST) {
+    return;
+  }
+  const api = new SauceLabs();
+  const response = await api.scDownload({
+    version: '5.2.2',
+    arch: 'arm64',
+    os: 'macos',
+  });
+
+  expect(response.download.version).toEqual('5.2.2');
+  expect(response.download.url).toMatch(/5\.2\.2/);
+  console.log(response.download.url);
+});
+
 test('should not be able to run Sauce Connect due to invalid credentials', async () => {
   if (SKIP_TEST) {
     return;
   }
-  const api = new SauceLabs({key: 'foobar'});
+  const api = new SauceLabs({key: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'});
   const err = await api
     .startSauceConnect({
       logger: console.log.bind(console),
       tunnelName: `node-saucelabs E2E test - ${ID}`,
     })
     .catch((err) => err);
-  expect(err.message).toContain('Unauthorized');
+  expect(err.message).toContain(
+    'Sauce Connect exited before reaching a ready state'
+  );
+});
+
+test('should not be able to run Sauce Connect due to missing tunnel-name', async () => {
+  if (SKIP_TEST) {
+    return;
+  }
+  const api = new SauceLabs();
+  const err = await api
+    .startSauceConnect({
+      logger: console.log.bind(console),
+    })
+    .catch((err) => err);
+  expect(err.message).toContain('Missing tunnel-name');
 });
 
 test('should be able to run Sauce Connect', async () => {
diff --git a/tests/__responses__/download_error.json b/tests/__responses__/download_error.json
new file mode 100644
index 0000000..9659283
--- /dev/null
+++ b/tests/__responses__/download_error.json
@@ -0,0 +1,6 @@
+{
+  "error": {
+    "code": "404",
+    "message": "Invalid input"
+  }
+}
diff --git a/tests/__responses__/download_macos.json b/tests/__responses__/download_macos.json
new file mode 100644
index 0000000..442a24f
--- /dev/null
+++ b/tests/__responses__/download_macos.json
@@ -0,0 +1,12 @@
+{
+  "download": {
+    "checksums": [
+      {
+        "value": "1384bb85b2d29d177933fc8e894c8f6ac60d83b666435d12e9fca7f50b350459",
+        "algorithm": "sha256"
+      }
+    ],
+    "url": "https://saucelabs.com/downloads/sauce-connect/5.2.2/sauce-connect-5.2.2_darwin.all.zip",
+    "version": "5.2.2"
+  }
+}
diff --git a/tests/__responses__/download_windows_x86_64.json b/tests/__responses__/download_windows_x86_64.json
new file mode 100644
index 0000000..0ee1cd5
--- /dev/null
+++ b/tests/__responses__/download_windows_x86_64.json
@@ -0,0 +1,12 @@
+{
+  "download": {
+    "checksums": [
+      {
+        "value": "fb932db5af5c4ed3dbdae9c939ae77da5d9440a3b0de60701643518af7b53ff1",
+        "algorithm": "sha256"
+      }
+    ],
+    "url": "https://saucelabs.com/downloads/sauce-connect/5.2.2/sauce-connect-5.2.2_windows.x86_64.zip",
+    "version": "5.2.2"
+  }
+}
diff --git a/tests/__responses__/versions.json b/tests/__responses__/versions.json
deleted file mode 100644
index 9ba2c6f..0000000
--- a/tests/__responses__/versions.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "Sauce Connect": {
-    "download_url": "https://wiki.saucelabs.com/display/DOCS/Setting+Up+Sauce+Connect",
-    "linux": {
-      "build": 5183,
-      "download_url": "https://saucelabs.com/downloads/sc-4.6.2-linux.tar.gz",
-      "sha1": "7b7f35433af9c3380758e048894d7b9aecf3754e"
-    },
-    "linux32": {
-      "build": 5183,
-      "download_url": "https://saucelabs.com/downloads/sc-4.6.2-linux32.tar.gz",
-      "sha1": "ce96d8543f013e0112a678aa9980e95331421cb1"
-    },
-    "osx": {
-      "build": 5183,
-      "download_url": "https://saucelabs.com/downloads/sc-4.6.2-osx.zip",
-      "sha1": "51c6c152eb54c20d8969a9d5917ebecc3ac868e1"
-    },
-    "version": "1.2.4",
-    "win32": {
-      "build": 5183,
-      "download_url": "https://saucelabs.com/downloads/sc-4.6.2-win32.zip",
-      "sha1": "4b8955c806998226ed41592cbe45c929c0f88a87"
-    }
-  },
-  "Sauce Connect 2": {
-    "download_url": "https://docs.saucelabs.com/reference/sauce-connect/",
-    "version": "4.3.13-r999"
-  }
-}
diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap
index 63ae680..b089835 100644
--- a/tests/__snapshots__/index.test.js.snap
+++ b/tests/__snapshots__/index.test.js.snap
@@ -1,5 +1,37 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should contain expected macos download link 1`] = `
+[
+  [
+    "https://api.us-west-1.saucelabs.com/rest/v1/public/tunnels/sauce-connect/download",
+    {
+      "responseType": "json",
+      "searchParams": {
+        "arch": "x86_64",
+        "os": "macos",
+        "version": "5.2.2",
+      },
+    },
+  ],
+]
+`;
+
+exports[`should contain expected windows download link 1`] = `
+[
+  [
+    "https://api.us-west-1.saucelabs.com/rest/v1/public/tunnels/sauce-connect/download",
+    {
+      "responseType": "json",
+      "searchParams": {
+        "arch": "x86_64",
+        "os": "windows",
+        "version": "5.2.2",
+      },
+    },
+  ],
+]
+`;
+
 exports[`should get builds failed jobs 1`] = `
 [
   [
@@ -122,32 +154,22 @@ exports[`should get user by username 1`] = `
 ]
 `;
 
-exports[`startSauceConnect should start sauce connect with fallback default version in case the call to the API failed 1`] = `
-[
-  [
-    "/foo/bar",
-    [
-      "--proxy-tunnel",
-      "--tunnel-name=my-tunnel",
-      "--user=foo",
-      "--api-key=bar",
-      "--region=us-west-1",
-    ],
-  ],
-]
-`;
-
 exports[`startSauceConnect should start sauce connect with proper parsed args 1`] = `
 [
   [
     "/foo/bar",
     [
-      "--proxy-tunnel",
-      "--verbose",
-      "--tunnel-name=my-tunnel",
-      "--user=foo",
-      "--api-key=bar",
+      "run",
+      "--proxy-tunnel=abc",
+      "--verbose=true",
+      "--username=foo",
+      "--access-key=bar",
+      "--proxy=http://example.com:8080",
+      "--proxy-sauce=http://example.com",
+      "--metadata=runner=example",
+      "--api-address=:8032",
       "--region=eu-central-1",
+      "--tunnel-name=my-tunnel",
     ],
   ],
 ]
diff --git a/tests/index.test.js b/tests/index.test.js
index ccb1336..79738e4 100644
--- a/tests/index.test.js
+++ b/tests/index.test.js
@@ -7,7 +7,9 @@ import FormData from 'form-data';
 
 import SauceLabs from '../src';
 
-import versions from './__responses__/versions.json';
+import downloadMacos from './__responses__/download_macos.json';
+import downloadWindows from './__responses__/download_windows_x86_64.json';
+import downloadError from './__responses__/download_error.json';
 
 const instances = [];
 
@@ -45,6 +47,21 @@ jest.mock('../src/sauceConnectLoader.js', () => {
   return SauceConnectLoaderMock;
 });
 
+jest.mock('../src/sauceConnectHealthcheck.js', () => {
+  class SauceConnectHealthcheckMock {
+    constructor() {
+      this.perform = jest.fn().mockResolvedValue(undefined);
+    }
+  }
+
+  return SauceConnectHealthcheckMock;
+});
+
+jest.mock('../src/constants.js', () => ({
+  ...jest.requireActual('../src/constants.js'),
+  SC_CLOSE_TIMEOUT: 10,
+}));
+
 const stdoutEmitter = spawn().stdout;
 const stderrEmitter = spawn().stderr;
 const origKill = process.kill;
@@ -53,6 +70,7 @@ beforeEach(() => {
   process.kill = jest.fn();
   // clean instances array
   instances.splice(0, instances.length);
+  // scHealthcheckPerformMock.mockRejectedValue(new Error("failure"))
 });
 test('should be inspectable', () => {
   const api = new SauceLabs({user: 'foo', key: 'bar'});
@@ -421,10 +439,70 @@ test('should fail if file parameter is invalid', async () => {
   expect(result.message).toContain('Invalid file parameter');
 });
 
+test('should contain expected macos download link', async () => {
+  const api = new SauceLabs({user: 'foo', key: 'bar'});
+  got.mockReturnValue(
+    Promise.resolve({
+      body: {
+        ...downloadMacos,
+      },
+    })
+  );
+  const scDownload = await api.scDownload({
+    version: '5.2.2',
+    os: 'macos',
+    arch: 'x86_64',
+  });
+  expect(got.mock.calls).toMatchSnapshot();
+  expect(scDownload.download).toMatchObject({
+    checksums: [
+      {
+        value:
+          '1384bb85b2d29d177933fc8e894c8f6ac60d83b666435d12e9fca7f50b350459',
+        algorithm: 'sha256',
+      },
+    ],
+    url: 'https://saucelabs.com/downloads/sauce-connect/5.2.2/sauce-connect-5.2.2_darwin.all.zip',
+    version: '5.2.2',
+  });
+});
+
+test('should contain expected windows download link', async () => {
+  const api = new SauceLabs({user: 'foo', key: 'bar'});
+  got.mockReturnValue(
+    Promise.resolve({
+      body: {
+        ...downloadWindows,
+      },
+    })
+  );
+  const scDownload = await api.scDownload({
+    version: '5.2.2',
+    os: 'windows',
+    arch: 'x86_64',
+  });
+  expect(got.mock.calls).toMatchSnapshot();
+  expect(scDownload.download).toMatchObject({
+    checksums: [
+      {
+        value:
+          'fb932db5af5c4ed3dbdae9c939ae77da5d9440a3b0de60701643518af7b53ff1',
+        algorithm: 'sha256',
+      },
+    ],
+    url: 'https://saucelabs.com/downloads/sauce-connect/5.2.2/sauce-connect-5.2.2_windows.x86_64.zip',
+    version: '5.2.2',
+  });
+});
+
 describe('startSauceConnect', () => {
   it('should start sauce connect with proper parsed args', async () => {
     const logs = [];
-    const api = new SauceLabs({user: 'foo', key: 'bar'});
+    const api = new SauceLabs({
+      user: 'foo',
+      key: 'bar',
+      proxy: 'http://example.com',
+    });
     setTimeout(
       () =>
         stdoutEmitter.emit(
@@ -435,114 +513,95 @@ describe('startSauceConnect', () => {
     );
     await api.startSauceConnect({
       scVersion: '1.2.3',
-      tunnelIdentifier: 'my-tunnel',
+      tunnelName: 'my-tunnel',
       'proxy-tunnel': 'abc',
+      metadata: 'runner=example',
       verbose: true,
       region: 'eu',
+      scUpstreamProxy: 'http://example.com:8080',
       logger: (log) => logs.push(log),
     });
     expect(spawn).toBeCalledTimes(1);
     expect(spawn.mock.calls).toMatchSnapshot();
 
     expect(logs).toHaveLength(1);
-    expect(instances).toHaveLength(1);
+    expect(instances).toHaveLength(2);
   });
 
-  it('should start sauce connect with latest version if no version is specified in the args', async () => {
+  it('should throw an error if there is an error response from the download API', async () => {
     const logs = [];
     const api = new SauceLabs({user: 'foo', key: 'bar'});
     got.mockReturnValue(
       Promise.resolve({
         body: {
-          data: {
-            ...versions,
-          },
+          ...downloadError,
         },
       })
     );
-    setTimeout(
-      () =>
-        stdoutEmitter.emit(
-          'data',
-          'Sauce Connect is up, you may start your tests'
-        ),
-      50
-    );
-    await api.startSauceConnect({
-      tunnelIdentifier: 'my-tunnel',
-      'proxy-tunnel': 'abc',
-      logger: (log) => logs.push(log),
-    });
+    const err = await api
+      .startSauceConnect({
+        tunnelName: 'my-tunnel',
+        'proxy-tunnel': 'abc',
+        logger: (log) => logs.push(log),
+      })
+      .catch((err) => err);
+    expect(err.message).toContain('code: 404 message: Invalid input');
   });
 
-  it('should start sauce connect with fallback default version in case the call to the API failed', async () => {
+  it('should throw an error if there is an invalid response from the download API', async () => {
     const logs = [];
     const api = new SauceLabs({user: 'foo', key: 'bar'});
-    got.mockImplementation(() => {
-      throw new Error('Endpoint not available!');
-    });
-    setTimeout(
-      () =>
-        stdoutEmitter.emit(
-          'data',
-          'Sauce Connect is up, you may start your tests'
-        ),
-      50
+    got.mockReturnValue(
+      Promise.resolve({
+        body: {}, // empty response
+      })
     );
-    await api.startSauceConnect({
-      tunnelIdentifier: 'my-tunnel',
-      'proxy-tunnel': 'abc',
-      logger: (log) => logs.push(log),
-    });
-    expect(spawn.mock.calls).toMatchSnapshot();
-  });
-
-  it('should properly fail if connection could not be established', async () => {
-    const errMessage = 'Sauce Connect could not establish a connection';
-    const api = new SauceLabs({user: 'foo', key: 'bar'});
-    setTimeout(() => stdoutEmitter.emit('data', errMessage), 50);
     const err = await api
       .startSauceConnect({
-        scVersion: '1.2.3',
-        tunnelIdentifier: 'my-tunnel',
+        tunnelName: 'my-tunnel',
         'proxy-tunnel': 'abc',
+        logger: (log) => logs.push(log),
       })
       .catch((err) => err);
-    expect(err.message).toBe(errMessage);
+    expect(err.message).toBe('Failed to retrieve Sauce Connect download.');
   });
 
-  it('should properly fail if user is not authorized', async () => {
-    const errMessage = 'Sauce Connect failed to start - 401 (Unauthorized).';
+  it('should throw an error if the call to the download API failed', async () => {
+    const logs = [];
     const api = new SauceLabs({user: 'foo', key: 'bar'});
-    setTimeout(() => stdoutEmitter.emit('data', errMessage), 50);
+    got.mockImplementation(() => {
+      throw new Error('Endpoint not available!');
+    });
     const err = await api
       .startSauceConnect({
-        scVersion: '1.2.3',
-        tunnelIdentifier: 'my-tunnel',
+        tunnelName: 'my-tunnel',
         'proxy-tunnel': 'abc',
+        logger: (log) => logs.push(log),
       })
       .catch((err) => err);
-    expect(err.message).toContain(errMessage);
+    expect(err.message).toContain('Endpoint not available!');
   });
 
-  it('should close sauce connect', async () => {
+  it('should start sauce connect with the default version if no version is specified in the args', async () => {
+    const logs = [];
     const api = new SauceLabs({user: 'foo', key: 'bar'});
-    setTimeout(
-      () =>
-        stdoutEmitter.emit(
-          'data',
-          'Sauce Connect is up, you may start your tests'
-        ),
-      50
-    );
-    const sc = await api.startSauceConnect(
-      {tunnelIdentifier: 'my-tunnel'},
-      true
+    got.mockReturnValue(
+      Promise.resolve({
+        body: {
+          ...downloadMacos,
+        },
+      })
     );
-    setTimeout(() => {
-      sc.cp.stdout.emit('data', 'Some other message');
-      sc.cp.stdout.emit('data', 'Goodbye');
-    }, 100);
+    await api.startSauceConnect({
+      tunnelName: 'my-tunnel',
+      'proxy-tunnel': 'abc',
+      logger: (log) => logs.push(log),
+    });
+  });
+
+  it('should close sauce connect', async () => {
+    const api = new SauceLabs({user: 'foo', key: 'bar'});
+    const sc = await api.startSauceConnect({tunnelName: 'my-tunnel'}, true);
     await sc.close();
     expect(process.kill).toBeCalledWith(123, 'SIGINT');
   });
@@ -551,29 +610,44 @@ describe('startSauceConnect', () => {
     const api = new SauceLabs({user: 'foo', key: 'bar'});
     setTimeout(() => stderrEmitter.emit('data', 'Uuups'), 50);
     const res = await api
-      .startSauceConnect({tunnelIdentifier: 'my-tunnel'})
+      .startSauceConnect({tunnelName: 'my-tunnel'})
       .catch((err) => err);
     expect(res).toEqual(new Error('Uuups'));
   });
 
-  it('should not fail if stderr is expected character', async () => {
+  it('should fail on Sauce Connect v4', async () => {
     const api = new SauceLabs({user: 'foo', key: 'bar'});
-    setTimeout(() => stderrEmitter.emit('data', '\u001b[K'), 50);
-    setTimeout(
-      () =>
-        stdoutEmitter.emit(
-          'data',
-          'Sauce Connect is up, you may start your tests'
-        ),
-      150
-    );
+    const scVersion = '4.9.2';
     const res = await api
-      .startSauceConnect({tunnelIdentifier: 'my-tunnel'})
+      .startSauceConnect({
+        tunnelName: 'my-tunnel',
+        scVersion: scVersion,
+      })
       .catch((err) => err);
-    expect(res instanceof Error).toBe(false);
+    expect(res).toEqual(
+      new Error(
+        `This Sauce Connect version (${scVersion}) is no longer supported. Please use Sauce Connect 5.`
+      )
+    );
   });
 });
 
+it('should fail with an invalid region', async () => {
+  const api = new SauceLabs({user: 'foo', key: 'bar', region: ''});
+  const res = await api
+    .startSauceConnect({
+      tunnelName: 'my-tunnel',
+    })
+    .catch((err) => err);
+  expect(res).toEqual(new Error(`Missing region`));
+});
+
+it('should fail when tunnelName is not given', async () => {
+  const api = new SauceLabs({user: 'foo', key: 'bar'});
+  const res = await api.startSauceConnect({}).catch((err) => err);
+  expect(res).toEqual(new Error(`Missing tunnel-name`));
+});
+
 test('should output failure msg for createJob API', async () => {
   const response = new Error('Response code 422 (Unprocessable Entity)');
   response.statusCode = 422;
@@ -584,7 +658,7 @@ test('should output failure msg for createJob API', async () => {
   const error = await api.createJob({framework: ''}).catch((err) => err);
 
   expect(error.message).toBe(
-    'Failed calling createJob: Response code 422 (Unprocessable Entity), empty framework'
+    'Failed calling createJob: Response code 422 (Unprocessable Entity), "empty framework"'
   );
 });
 
@@ -600,6 +674,19 @@ test('should get user by username', async () => {
   expect(got.mock.calls[0]).toMatchSnapshot();
 });
 
+test('should get user by username fail when api fails', async () => {
+  const api = new SauceLabs({user: 'foo', key: 'bar'});
+  got.mockReturnValue(Promise.reject(new Error('example')));
+  const error = await api
+    .getUserByUsername({username: 'fooUser'})
+    .catch((err) => err);
+  expect(error).toEqual(
+    new Error(
+      'There was an error while fetching user information: Failed calling getUsersV1: example, undefined'
+    )
+  );
+});
+
 test('should get list of builds', async () => {
   const api = new SauceLabs({user: 'foo', key: 'bar'});
   got
diff --git a/tests/sauceConnectHealthcheck.test.js b/tests/sauceConnectHealthcheck.test.js
new file mode 100644
index 0000000..536e641
--- /dev/null
+++ b/tests/sauceConnectHealthcheck.test.js
@@ -0,0 +1,30 @@
+import SauceConnectHealthCheck from '../src/sauceConnectHealthcheck';
+
+const fetchMock = jest.fn();
+global.fetch = fetchMock;
+
+describe('SauceConnectHealthcheck', () => {
+  beforeEach(() => {
+    fetchMock.mockClear();
+  });
+
+  describe('perform', () => {
+    test('raises error if response code != 200', async () => {
+      fetchMock.mockImplementation(async () => ({
+        status: 503,
+      }));
+      const healthcheck = new SauceConnectHealthCheck();
+      const error = await healthcheck.perform(':8042').catch((err) => err);
+      expect(error.message).toBe('response status code 503 != 200');
+    });
+
+    test('all good when response code == 200', async () => {
+      fetchMock.mockImplementation(async () => ({
+        status: 200,
+      }));
+      const healthcheck = new SauceConnectHealthCheck();
+      const result = await healthcheck.perform(':8042');
+      expect(result).toBe(undefined);
+    });
+  });
+});
diff --git a/tests/sauceConnectLoader.test.js b/tests/sauceConnectLoader.test.js
index 938e3a0..5eebaf1 100644
--- a/tests/sauceConnectLoader.test.js
+++ b/tests/sauceConnectLoader.test.js
@@ -6,11 +6,30 @@ import compressing from 'compressing';
 
 describe('SauceConnectLoader', () => {
   describe('constructor', () => {
-    test('should throw if platform is unsupported', () => {
-      jest.spyOn(utils, 'getPlatform').mockImplementation(() => 'whatever');
-      expect(
-        () => new SauceConnectLoader({sauceConnectVersion: '1.2.3'})
-      ).toThrow(ReferenceError, 'Unsupported platform whatever');
+    test('should have .exe path for windows platform', () => {
+      // Enable monkey patching process.platform.
+      const originalPlatform = process.platform;
+      let platform = 'win32';
+      Object.defineProperty(process, 'platform', {get: () => platform});
+
+      const scl = new SauceConnectLoader('1.2.3');
+      expect(scl.path).toContain('exe');
+
+      // Restore the original value of process.platform.
+      platform = originalPlatform;
+    });
+
+    test('should not have .exe path for non-windows platform', () => {
+      // Enable monkey patching process.platform.
+      const originalPlatform = process.platform;
+      let platform = 'linux';
+      Object.defineProperty(process, 'platform', {get: () => platform});
+
+      const scl = new SauceConnectLoader('1.2.3');
+      expect(scl.path).not.toContain('exe');
+
+      // Restore the original value of process.platform.
+      platform = originalPlatform;
     });
   });
 
@@ -29,9 +48,7 @@ describe('SauceConnectLoader', () => {
           jest
             .spyOn(fs.promises, 'stat')
             .mockImplementation(() => Promise.resolve());
-          scl = new SauceConnectLoader({
-            sauceConnectVersion: '1.2.3',
-          });
+          scl = new SauceConnectLoader('1.2.3');
         });
 
         test('should not attempt to download anything', async () => {
@@ -45,14 +62,18 @@ describe('SauceConnectLoader', () => {
           jest
             .spyOn(fs.promises, 'stat')
             .mockImplementation(() => Promise.reject({code: 'ENOENT'}));
-          scl = new SauceConnectLoader({
-            sauceConnectVersion: '1.2.3',
-          });
+          scl = new SauceConnectLoader('1.2.3');
         });
 
         test('should attempt to download', async () => {
+          let url = 'https://this-is-a-test.com';
+          await scl.verifyAlreadyDownloaded({url: url});
+          expect(scl._download).toHaveBeenCalledWith(url);
+        });
+
+        test('should not attempt to download when url is not provided', async () => {
           await scl.verifyAlreadyDownloaded();
-          expect(scl._download).toHaveBeenCalled();
+          expect(scl._download).not.toHaveBeenCalled();
         });
       });
 
@@ -61,9 +82,7 @@ describe('SauceConnectLoader', () => {
           jest
             .spyOn(fs.promises, 'stat')
             .mockImplementation(() => Promise.reject(new Error()));
-          scl = new SauceConnectLoader({
-            sauceConnectVersion: '1.2.3',
-          });
+          scl = new SauceConnectLoader('1.2.3');
         });
 
         test('should reject', () => {
@@ -86,7 +105,7 @@ describe('SauceConnectLoader', () => {
         jest
           .spyOn(fs.promises, 'rename')
           .mockImplementation(() => Promise.resolve());
-        scl = new SauceConnectLoader({sauceConnectVersion: '1.2.3'});
+        scl = new SauceConnectLoader('1.2.3');
         mockHttpsGet = jest.spyOn(https, 'get');
 
         mockCompressingLinux = jest
@@ -99,33 +118,43 @@ describe('SauceConnectLoader', () => {
 
       test('should download and uncompress - linux', async () => {
         jest.spyOn(utils, 'getPlatform').mockImplementation(() => 'linux');
-        await scl._download();
-        expect(mockHttpsGet).toHaveBeenCalledWith(
-          scl.url,
-          expect.any(Function)
-        );
+
+        // linux downloads have .tar.gz compression
+        const url = 'https://httpbin.org/get/some_file.tar.gz';
+        await scl._download(url);
+        expect(mockHttpsGet).toHaveBeenCalledWith(url, expect.any(Function));
         expect(mockCompressingLinux).toHaveBeenCalled();
       });
 
-      test('should download and uncompress - mac', async () => {
-        jest.spyOn(utils, 'getPlatform').mockImplementation(() => 'darwin');
-        await scl._download();
-        expect(mockHttpsGet).toHaveBeenCalledWith(
-          scl.url,
-          expect.any(Function)
-        );
+      test('should download and uncompress - macos', async () => {
+        jest.spyOn(utils, 'isWindows').mockImplementation(() => false);
+
+        // macos downloads have .zip compression
+        const url = 'https://httpbin.org/get/some_file.zip';
+        await scl._download(url);
+        expect(mockHttpsGet).toHaveBeenCalledWith(url, expect.any(Function));
         expect(mockCompressingWinMac).toHaveBeenCalled();
       });
 
-      test('should download and uncompress - win', async () => {
-        jest.spyOn(utils, 'getPlatform').mockImplementation(() => 'win32');
-        await scl._download();
-        expect(mockHttpsGet).toHaveBeenCalledWith(
-          scl.url,
-          expect.any(Function)
-        );
+      test('should download and uncompress - windows', async () => {
+        jest.spyOn(utils, 'isWindows').mockImplementation(() => true);
+
+        // windows downloads have .zip compression
+        const url = 'https://httpbin.org/get/some_file.zip';
+        await scl._download(url);
+        expect(mockHttpsGet).toHaveBeenCalledWith(url, expect.any(Function));
         expect(mockCompressingWinMac).toHaveBeenCalled();
       });
     });
+
+    describe('_download()', () => {
+      test('should handle download error', async () => {
+        const scl = new SauceConnectLoader('1.2.3');
+        const url = 'https://this-will-not-resolve.penguin';
+        ``;
+        const err = await scl._download(url).catch((err) => err);
+        expect(err.message).toContain('this-will-not-resolve.penguin');
+      });
+    });
   });
 });
diff --git a/tests/sauceConnectManager.test.js b/tests/sauceConnectManager.test.js
new file mode 100644
index 0000000..158bb61
--- /dev/null
+++ b/tests/sauceConnectManager.test.js
@@ -0,0 +1,69 @@
+import {SauceConnectManager} from '../src/sauceConnectManager';
+import * as constants from '../src/constants';
+
+describe('SauceConnectManager', () => {
+  describe('waitForReady', () => {
+    test('process terminated with exitCode', async () => {
+      const manager = new SauceConnectManager({
+        exitCode: 1,
+        stderr: {
+          on: jest.fn(),
+        },
+        stdout: {
+          on: jest.fn(),
+        },
+      });
+      const error = await manager.waitForReady(':8042').catch((err) => err);
+      expect(error.message).toBe(
+        'Sauce Connect exited before reaching a ready state'
+      );
+    });
+
+    describe('waiting too long for healthcheck', () => {
+      let origTimeout = constants.SC_READY_TIMEOUT;
+      beforeEach(() => {
+        // eslint-disable-next-line no-import-assign
+        Object.defineProperty(constants, 'SC_READY_TIMEOUT', {value: 10});
+      });
+
+      test('waiting too long for healthcheck', async () => {
+        const manager = new SauceConnectManager({
+          stderr: {
+            on: jest.fn(),
+          },
+          stdout: {
+            on: jest.fn(),
+          },
+        });
+        const error = await manager.waitForReady(':8042').catch((err) => err);
+        expect(error.message).toBe('Timeout waiting for healthcheck endpoint');
+      });
+
+      afterEach(() => {
+        // eslint-disable-next-line no-import-assign
+        Object.defineProperty(constants, 'SC_READY_TIMEOUT', {
+          value: origTimeout,
+        });
+      });
+    });
+  });
+
+  describe('close', () => {
+    test('process terminated with exitCode', async () => {
+      const manager = new SauceConnectManager({
+        exitCode: 1,
+      });
+      const result = await manager.close();
+      expect(result).toBe(undefined);
+    });
+
+    test('reacts to process exit event', async () => {
+      const manager = new SauceConnectManager({
+        pid: 123,
+        on: (event, callback) => setTimeout(callback, 10),
+      });
+
+      await manager.close();
+    });
+  });
+});
diff --git a/tests/typings/test.ts b/tests/typings/test.ts
index 23ad5e7..9975fdb 100644
--- a/tests/typings/test.ts
+++ b/tests/typings/test.ts
@@ -12,8 +12,8 @@ async function foobar() {
   console.log(job.selenium_version);
 
   const sc = await api.startSauceConnect({
-    scVersion: '4.5.4',
-    tunnelIdentifier: '1234',
+    scVersion: '5.1.3',
+    tunnelName: '1234',
     logger: (output: string) => console.log(output),
   });
   sc.cp.pid;
diff --git a/tests/utils.test.js b/tests/utils.test.js
index 9925399..4b55b08 100644
--- a/tests/utils.test.js
+++ b/tests/utils.test.js
@@ -9,6 +9,7 @@ import {
   getStrictSsl,
   getAssetHost,
   createProxyAgent,
+  getCPUArch,
   getPlatform,
 } from '../src/utils';
 
@@ -19,7 +20,46 @@ test('createHMAC', async () => {
 });
 
 test('getPlatform', () => {
-  expect(getPlatform()).toBe(process.platform);
+  // Enable monkey patching process.platform.
+  const originalPlatform = process.platform;
+  let platform = null;
+  Object.defineProperty(process, 'platform', {get: () => platform});
+
+  platform = 'win32';
+  expect(getPlatform()).toBe('windows');
+
+  platform = 'darwin';
+  expect(getPlatform()).toBe('macos');
+
+  platform = 'linux';
+  expect(getPlatform()).toBe('linux');
+
+  // Restore the original value of process.platform.
+  platform = originalPlatform;
+});
+
+test('getCPUArch', () => {
+  let result = process.arch;
+  if (result == 'x64') {
+    result = 'x86_64';
+  }
+  expect(getCPUArch()).toBe(result);
+});
+
+test('getCPUArch', () => {
+  // Enable monkey patching process.arch.
+  const originalArch = process.arch;
+  let arch = null;
+  Object.defineProperty(process, 'arch', {get: () => arch});
+
+  arch = 'x64';
+  expect(getCPUArch()).toBe('x86_64');
+
+  arch = 'arm64';
+  expect(getCPUArch()).toBe('arm64');
+
+  // Restore the original value of process.platform.
+  arch = originalArch;
 });
 
 test('getAPIHost', () => {