Repository: elastic/synthetics
Pull request #748 (2023-04-25 04:20:56)

chore: upgrade all non dev deps to latest
+ Upgrades all non dev deps. 


Patch:
diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile
deleted file mode 100644
index 4ada75d..0000000
--- a/.ci/Jenkinsfile
+++ /dev/null
@@ -1,208 +0,0 @@
-#!/usr/bin/env groovy
-
-@Library('apm@current') _
-
-pipeline {
-  agent { label 'ubuntu-20 && immutable' }
-  environment {
-    REPO = "synthetics"
-    BASE_DIR = "src/github.com/elastic/${env.REPO}"
-    PIPELINE_LOG_LEVEL = 'INFO'
-    DOCKERELASTIC_SECRET = 'secret/observability-team/ci/docker-registry/prod'
-    DOCKER_REGISTRY = 'docker.elastic.co'
-    NODE_DOCKER_IMAGE = "${env.DOCKER_REGISTRY}/observability-ci/node-cypress:latest"
-    SECCOMP_FILE = "${env.WORKSPACE}/${env.BASE_DIR}/.ci/seccomp_profile.json"
-    NPMRC_SECRET = 'secret/jenkins-ci/npmjs/elasticmachine'
-    TOTP_SECRET = 'totp/code/npmjs-elasticmachine'
-    SLACK_CHANNEL = '#synthetics-user_experience-uptime'
-  }
-  options {
-    timeout(time: 12, unit: 'HOURS')  // to support releases then we will add a timeout in each stage
-    buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20', daysToKeepStr: '30'))
-    timestamps()
-    ansiColor('xterm')
-    disableResume()
-    durabilityHint('PERFORMANCE_OPTIMIZED')
-    rateLimitBuilds(throttle: [count: 60, durationName: 'hour', userBoost: true])
-    quietPeriod(10)
-  }
-  triggers {
-    issueCommentTrigger("${obltGitHubComments()}")
-    upstream("apm-agent-rum/heartbeat-elastic-synthetics/${ env.JOB_BASE_NAME.startsWith('PR-') ? 'none' : 'heartbeat-synthetics' }, apm-agent-rum/heartbeat-elastic-synthetics/${ env.JOB_BASE_NAME.startsWith('PR-') ? 'none' : 'heartbeat-synthetics' }-7.x")
-  }
-  parameters {
-    booleanParam(name: 'run_all_stages', defaultValue: false, description: 'Force to run all stages.')
-    booleanParam(name: 'release', defaultValue: false, description: 'Release. If so, all the other parameters will be ignored when releasing from main.')
-  }
-  stages {
-    /**
-     Checkout the code and stash it, to use it on other stages.
-     */
-    stage('Checkout') {
-      options {
-        skipDefaultCheckout()
-        timeout(5)
-      }
-      steps {
-        deleteDir()
-        gitCheckout(basedir: "${BASE_DIR}")
-        retryWithSleep(retries: 3, seconds: 5, backoff: true) {
-          dockerLogin(secret: "${DOCKERELASTIC_SECRET}",
-                      registry: "${DOCKER_REGISTRY}")
-          withNodeJSEnv(){
-            dir("${BASE_DIR}"){
-              sh(label: 'Download dependencies',script: 'npm install')
-            }
-          }
-        }
-        stash allowEmpty: true, name: 'source', useDefaultExcludes: false, excludes: ".nvm/**,.npm/_cacache/**,.nvm/.git/**"
-      }
-    }
-    stage('Release') {
-      options {
-        skipDefaultCheckout()
-      }
-      when {
-        beforeAgent true
-        allOf {
-          branch 'main'
-          expression { return params.release }
-        }
-      }
-      agent { label 'ubuntu-20 && immutable' }
-      stages {
-        stage('Approve to Release') {
-          options { skipDefaultCheckout() }
-          steps {
-            notifyStatus(slackStatus: 'warning', subject: "[${env.REPO}] Release ready to be pushed",
-                         body: "Please go to (<${env.BUILD_URL}input|here>) to approve or reject within 12 hours.")
-            setEnvVar('RELEASE', prompt(message: 'Should we release a new version?'))
-          }
-        }
-        stage('Release CI') {
-          when {
-            beforeAgent true
-            expression { return env.RELEASE == 'true' }
-          }
-          options { skipDefaultCheckout() }
-          environment {
-            NPM_PACKAGE = '@elastic/synthetics'
-          }
-          steps {
-            cleanup()
-            withNodeJSEnv() {
-              dir("${BASE_DIR}") {
-                withNpmrc(secret: "${env.NPMRC_SECRET}", path: "${env.WORKSPACE}/${env.BASE_DIR}") {
-                  withGitRelease(credentialsId: '2a9602aa-ab9f-4e52-baf3-b71ca88469c7-UserAndToken') {
-                    sh(script: 'npm run clean', label: 'clean release')
-                    sh(script: 'npm ci', label: 'prepare the ci environment')
-                    setEnvVar('VERSION', sh(script: 'npm version prerelease --preid=beta', label: 'bump the beta version', returnStdout: true).trim())
-                    gitPush()
-                    gitPush(args: '--tags')
-                    log(level: 'INFO', text: "Version to be published '${env.VERSION}'")
-                    withTotpVault(secret: "${env.TOTP_SECRET}", code_var_name: 'TOTP_CODE'){
-                      sh(script: "npm publish --otp=${env.TOTP_CODE} --tag beta", label: 'publish to NPM')
-                    }
-                    withTotpVault(secret: "${env.TOTP_SECRET}", code_var_name: 'TOTP_CODE'){
-                      sh(script: "npm dist-tag --otp=${env.TOTP_CODE} add ${env.NPM_PACKAGE}@${env.VERSION} latest", label: 'retag latest')
-                    }
-                  }
-                }
-              }
-            }
-          }
-          post {
-            success {
-              notifyStatus(slackStatus: 'good', subject: "[${env.REPO}] Release '${env.VERSION}' published", body: "Great news, the release has been done successfully. (<${env.RUN_DISPLAY_URL}|Open>). \n Release URL: (<https://preview.npmjs.com/package/${env.NPM_PACKAGE}/v/${env.VERSION}|${env.VERSION}>)")
-            }
-            always {
-              script {
-                currentBuild.description = "${currentBuild.description?.trim() ? currentBuild.description : ''} released"
-              }
-            }
-            failure {
-              notifyStatus(slackStatus: 'danger', subject: "[${env.REPO}] Release '${env.VERSION?.trim()}' failed", body: "(<${env.RUN_DISPLAY_URL}|Open>)")
-            }
-          }
-        }
-        stage('Approve to Stack Release') {
-          when {
-            beforeAgent true
-            expression { return env.RELEASE == 'true' }
-          }
-          options { skipDefaultCheckout() }
-          steps {
-            notifyStatus(slackStatus: 'warning', subject: "[${env.REPO}] Stack Release ready to be pushed",
-                         body: "Please go to (<${env.BUILD_URL}input|here>) to approve or reject within 12 hours.")
-            setEnvVar('STACK_RELEASE', prompt(message: 'Should we tag the current version as a stack release?'))
-          }
-        }
-        stage('Stack Release CI') {
-          when {
-            beforeAgent true
-            expression { return env.RELEASE == 'true' && env.STACK_RELEASE == 'true' }
-          }
-          options { skipDefaultCheckout() }
-          environment {
-            NPM_PACKAGE = '@elastic/synthetics'
-          }
-          steps {
-            cleanup()
-            withNodeJSEnv() {
-              dir("${BASE_DIR}") {
-                withNpmrc(secret: "${env.NPMRC_SECRET}", path: "${env.WORKSPACE}/${env.BASE_DIR}") {
-                  withTotpVault(secret: "${env.TOTP_SECRET}", code_var_name: 'TOTP_CODE') {
-                    setEnvVar('VERSION', sh(script: 'npm show . version', label: 'tag the version as stack release', returnStdout: true).trim())
-                    sh(script: "npm dist-tag --otp=${env.TOTP_CODE} add ${env.NPM_PACKAGE}@${env.VERSION} stack_release", label: 'tag stack_release')
-                  }
-                }
-              }
-            }
-          }
-          post {
-            success {
-              notifyStatus(slackStatus: 'good', subject: "[${env.REPO}] Stack Release '${env.VERSION}' published", body: "Great news, the stack release has been done successfully. (<${env.RUN_DISPLAY_URL}|Open>). \n Release URL: (<https://preview.npmjs.com/package/${env.NPM_PACKAGE}/v/${env.VERSION}|${env.VERSION}>)")
-            }
-            always {
-              script {
-                currentBuild.description = "${currentBuild.description?.trim() ? currentBuild.description : ''} released"
-              }
-            }
-            failure {
-              notifyStatus(slackStatus: 'danger', subject: "[${env.REPO}] Stack Release '${env.VERSION?.trim()}' failed", body: "(<${env.RUN_DISPLAY_URL}|Open>)")
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-def cleanup(){
-  dir("${BASE_DIR}") {
-    deleteDir()
-  }
-  unstash 'source'
-}
-
-def withNodeInDockerEnv(Map args=[:], Closure body) {
-  docker.image("${NODE_DOCKER_IMAGE}").inside(" --security-opt seccomp=${SECCOMP_FILE}"){
-    withEnv(["HOME=${WORKSPACE}"]) {
-      body()
-    }
-  }
-}
-
-/**
-  This is the wrapper to send notifications for the release process through
-  slack and email, since it requires some formatting to support the same
-  message in both systems.
- */
-def notifyStatus(def args = [:]) {
-  releaseNotification(slackChannel: "${env.SLACK_CHANNEL}",
-                      slackColor: args.slackStatus,
-                      slackCredentialsId: 'jenkins-slack-integration-token',
-                      to: 'synthrum@elastic.co',
-                      subject: args.subject,
-                      body: args.body)
-}
diff --git a/.ci/dist-tag.groovy b/.ci/dist-tag.groovy
deleted file mode 100644
index 63eacf5..0000000
--- a/.ci/dist-tag.groovy
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env groovy
-
-@Library('apm@current') _
-
-pipeline {
-  agent { label 'linux && immutable' }
-  environment {
-    REPO = "synthetics"
-    BASE_DIR = "src/github.com/elastic/${env.REPO}"
-    NPMRC_SECRET = 'secret/jenkins-ci/npmjs/elasticmachine'
-    TOTP_SECRET = 'totp/code/npmjs-elasticmachine'
-    NPM_PACKAGE = '@elastic/synthetics'
-    SLACK_CHANNEL = '#synthetics-user_experience-uptime'
-    VERSION = "${params.VERSION}"
-    DIST_TAG = "${params.DIST_TAG}"
-  }
-  options {
-    timeout(time: 1, unit: 'HOURS')  // to support releases then we will add a timeout in each stage
-    buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20', daysToKeepStr: '30'))
-    timestamps()
-    ansiColor('xterm')
-    disableResume()
-    durabilityHint('PERFORMANCE_OPTIMIZED')
-    rateLimitBuilds(throttle: [count: 60, durationName: 'hour', userBoost: true])
-    quietPeriod(10)
-  }
-  parameters {
-    string(name: 'VERSION', defaultValue: '', description: 'Version to dist tag.')
-    string(name: 'DIST_TAG', defaultValue: '', description: 'Dist tag to use.')
-  }
-  stages {
-    stage('Checks') {
-      options {
-        skipDefaultCheckout()
-        timeout(5)
-      }
-      steps {
-        whenTrue(isEmpty(env.VERSION)) {
-          error("VERSION cannot be empty")
-        }
-        whenTrue(isEmpty(env.DIST_TAG)) {
-          error("DIST_TAG cannot be empty")
-        }
-      }
-    }
-    stage('Checkout') {
-      options {
-        skipDefaultCheckout()
-        timeout(5)
-      }
-      steps {
-        deleteDir()
-        gitCheckout(basedir: "${BASE_DIR}")
-      }
-    }
-    stage('Dist tag') {
-      options {
-        skipDefaultCheckout()
-      }
-      steps {
-        withNodeJSEnv() {
-          dir("${BASE_DIR}") {
-            withNpmrc(secret: "${env.NPMRC_SECRET}", path: "${env.WORKSPACE}/${env.BASE_DIR}") {
-              withTotpVault(secret: "${env.TOTP_SECRET}", code_var_name: 'TOTP_CODE') {
-                sh(script: "npm dist-tag --otp=${env.TOTP_CODE} add ${env.NPM_PACKAGE}@${env.VERSION} ${env.DIST_TAG}", label: "tag ${env.DIST_TAG}")
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-  post {
-    cleanup {
-      notifyBuildResult(slackComment: true)
-    }
-  }
-}
diff --git a/.ci/jobs/defaults.yml b/.ci/jobs/defaults.yml
deleted file mode 100644
index f2d13cc..0000000
--- a/.ci/jobs/defaults.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-
-##### GLOBAL METADATA
-
-- meta:
-    cluster: apm-ci
-
-##### JOB DEFAULTS
-
-- job:
-    logrotate:
-      numToKeep: 20
-    node: linux
-    concurrent: true
-    publishers:
-      - email:
-          recipients: infra-root+build@elastic.co
-    prune-dead-branches: true
diff --git a/.ci/jobs/dist-tag-mbp.yml b/.ci/jobs/dist-tag-mbp.yml
deleted file mode 100644
index 26096cb..0000000
--- a/.ci/jobs/dist-tag-mbp.yml
+++ /dev/null
@@ -1,42 +0,0 @@
----
-- job:
-    name: apm-agent-rum/dist-tag-mbp
-    display-name: Dist tag a version for Elastic Synthetics
-    description: Dist tag a version for Elastic Synthetics.
-    project-type: multibranch
-    script-path: .ci/dist-tag.groovy
-    scm:
-      - github:
-          branch-discovery: no-pr
-          discover-pr-forks-strategy: merge-current
-          discover-pr-forks-trust: permission
-          discover-pr-origin: merge-current
-          discover-tags: false
-          disable-pr-notifications: true
-          head-filter-regex: '^(main|PR-562)$'
-          notification-context: 'elastic-synthetics'
-          repo: synthetics
-          repo-owner: elastic
-          credentials-id: 2a9602aa-ab9f-4e52-baf3-b71ca88469c7-UserAndToken
-          ssh-checkout:
-            credentials: f6c7695a-671e-4f4f-a331-acdce44ff9ba
-          build-strategies:
-            - regular-branches: true
-          property-strategies:
-            all-branches:
-              - suppress-scm-triggering: true
-          clean:
-              after: true
-              before: true
-          prune: true
-          shallow-clone: true
-          depth: 3
-          do-not-fetch-tags: true
-          submodule:
-              disable: false
-              recursive: true
-              parent-credentials: true
-              timeout: 100
-          timeout: '15'
-          use-author: true
-          wipe-workspace: true
diff --git a/.ci/jobs/heartbeat-synthetics.yml b/.ci/jobs/heartbeat-synthetics.yml
deleted file mode 100644
index 230534a..0000000
--- a/.ci/jobs/heartbeat-synthetics.yml
+++ /dev/null
@@ -1,48 +0,0 @@
----
-- job:
-    name: apm-agent-rum/heartbeat-elastic-synthetics
-    display-name: Heartbeat Packaging for Elastic Synthetics
-    description: Make the packages for Heartbeat and publish them.
-    project-type: multibranch
-    script-path: .ci/heartbeat-synthetics.groovy
-    scm:
-      - github:
-          branch-discovery: 'all'
-          discover-pr-forks-strategy: 'merge-current'
-          discover-pr-forks-trust: 'permission'
-          discover-pr-origin: 'merge-current'
-          discover-tags: false
-          head-filter-regex: 'heartbeat-synthetics.*'
-          disable-pr-notifications: true
-          notification-context: 'heartbeat-synthetics'
-          repo: 'beats'
-          repo-owner: 'elastic'
-          credentials-id: 2a9602aa-ab9f-4e52-baf3-b71ca88469c7-UserAndToken
-          ssh-checkout:
-            credentials: f6c7695a-671e-4f4f-a331-acdce44ff9ba
-          build-strategies:
-          - skip-initial-build: true
-          - tags:
-              ignore-tags-older-than: -1
-              ignore-tags-newer-than: 30
-          - named-branches:
-              - regex-name:
-                  regex: 'heartbeat-synthetics.*'
-                  case-sensitive: true
-          - change-request:
-              ignore-target-only-changes: true
-          clean:
-              after: true
-              before: true
-          prune: true
-          shallow-clone: true
-          depth: 3
-          do-not-fetch-tags: true
-          submodule:
-              disable: false
-              recursive: true
-              parent-credentials: true
-              timeout: 100
-          timeout: '15'
-          use-author: true
-          wipe-workspace: true
diff --git a/.ci/jobs/synthetic-monitoring.yml b/.ci/jobs/synthetic-monitoring.yml
deleted file mode 100644
index 40b7afd..0000000
--- a/.ci/jobs/synthetic-monitoring.yml
+++ /dev/null
@@ -1,43 +0,0 @@
----
-- job:
-    name: apm-agent-rum/elastic-synthetics
-    display-name: Elastic Synthetics
-    description: Jenkins pipeline for the Elastic Synthetics project.
-    project-type: multibranch
-    script-path: .ci/Jenkinsfile
-    scm:
-      - github:
-          branch-discovery: no-pr
-          discover-pr-forks-strategy: merge-current
-          discover-pr-forks-trust: permission
-          discover-pr-origin: merge-current
-          discover-tags: true
-          head-filter-regex: '(main|\d+\.\d+\.\d+|PR-.*|v\d+\.\d+\.\d+)'
-          notification-context: 'elastic-synthetics'
-          repo: synthetics
-          repo-owner: elastic
-          credentials-id: 2a9602aa-ab9f-4e52-baf3-b71ca88469c7-UserAndToken
-          ssh-checkout:
-            credentials: f6c7695a-671e-4f4f-a331-acdce44ff9ba
-          build-strategies:
-            - tags:
-                ignore-tags-older-than: -1
-                ignore-tags-newer-than: -1
-            - regular-branches: true
-            - change-request:
-                ignore-target-only-changes: false
-          clean:
-            after: true
-            before: true
-          prune: true
-          shallow-clone: true
-          depth: 4
-          do-not-fetch-tags: true
-          submodule:
-            disable: false
-            recursive: true
-            parent-credentials: true
-            timeout: 100
-          timeout: '15'
-          use-author: true
-          wipe-workspace: 'True'
diff --git a/.github/actions/setup-totp/action.yml b/.github/actions/setup-totp/action.yml
new file mode 100644
index 0000000..861e111
--- /dev/null
+++ b/.github/actions/setup-totp/action.yml
@@ -0,0 +1,34 @@
+name: setup-totp
+description: Set up TOTP_CODE environment variable.
+inputs:
+  vault-url:
+    required: true
+    description: Vault URL
+  vault-role-id:
+    required: true
+    description: Vault Role ID
+  vault-secret-id:
+    required: true
+    description: Vault Secret ID
+  secret:
+    default: totp/code/npmjs-elasticmachine
+    description: Secret path
+  secret-key:
+    default: code
+    description: Secret key
+  env-name:
+    default: TOTP_CODE
+    description: Environment variable name
+
+runs:
+  using: composite
+  steps:
+    - name: Set up TOTP_CODE
+      uses: hashicorp/vault-action@v2.5.0
+      with:
+        url: ${{ secrets.VAULT_ADDR }}
+        roleId: ${{ secrets.VAULT_ROLE_ID }}
+        secretId: ${{ secrets.VAULT_SECRET_ID }}
+        method: approle
+        secrets: |
+          ${{ inputs.secret }} ${{ inputs.secret-key }} | ${{ inputs.env-name }};
diff --git a/.github/workflows/dist-tag.yml b/.github/workflows/dist-tag.yml
new file mode 100644
index 0000000..51327fc
--- /dev/null
+++ b/.github/workflows/dist-tag.yml
@@ -0,0 +1,34 @@
+name: dist-tag
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        type: string
+        description: Version to dist tag.
+        required: true
+      dist-tag:
+        type: string
+        description: Dist tag to use.
+        required: true
+
+jobs:
+  dist-tag:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: elastic/apm-pipeline-library/.github/actions/setup-npmrc@current
+        with:
+          secret: secret/jenkins-ci/npmjs/elasticmachine
+          vault-url: ${{ secrets.VAULT_ADDR }}
+          vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
+          vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
+      - uses: ./.github/actions/setup-totp
+        with:
+          vault-url: ${{ secrets.VAULT_ADDR }}
+          vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
+          vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
+      - name: NPM dist-tag
+        run: |
+          NPM_PACKAGE=$(jq '.name' package.json)
+          npm dist-tag --otp=${TOTP_CODE} add ${NPM_PACKAGE}@${{ github.event.inputs.version }} ${{ github.event.inputs.dist-tag }}
diff --git a/.github/workflows/opentelemetry.yml b/.github/workflows/opentelemetry.yml
index 6ea3617..b246572 100644
--- a/.github/workflows/opentelemetry.yml
+++ b/.github/workflows/opentelemetry.yml
@@ -5,7 +5,9 @@ on:
   workflow_run:
     workflows:
       - ci
+      - dist-tag
       - e2e
+      - release
     types: [completed]
 
 jobs:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..5cd31fb
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,60 @@
+name: release
+
+on:
+  workflow_dispatch:
+    inputs:
+      is-stack-release:
+        type: boolean
+        description: Should we tag the current version as a stack release?
+        default: false
+
+permissions:
+  contents: write
+
+env:
+   GH_TOKEN: ${{ github.token }}
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: elastic/apm-pipeline-library/.github/actions/setup-git@current
+      - uses: elastic/apm-pipeline-library/.github/actions/setup-npmrc@current
+        with:
+          secret: secret/jenkins-ci/npmjs/elasticmachine
+          vault-url: ${{ secrets.VAULT_ADDR }}
+          vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
+          vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
+      - run: npm run clean
+      - run: npm ci
+      - name: Bump the beta version
+        run: |
+          echo "VERSION=$(npm version prerelease --preid=beta)" >> $GITHUB_ENV
+          echo "NPM_PACKAGE=$(jq '.name' package.json) >> $GITHUB_ENV
+      - run: git push --tags
+      - uses: ./.github/actions/setup-totp
+        with:
+          vault-url: ${{ secrets.VAULT_ADDR }}
+          vault-role-id: ${{ secrets.VAULT_ROLE_ID }}
+          vault-secret-id: ${{ secrets.VAULT_SECRET_ID }}
+      - name: NPM Publish
+        run: npm publish --otp=${TOTP_CODE} --tag beta
+      - name: Create 'latest' dist-tag
+        run: npm dist-tag --otp=${TOTP_CODE} add "${NPM_PACKAGE}@${VERSION}" latest
+      - name: Create 'stack_release' dist-tag
+        if: ${{ github.event.inputs.is-stack-release }}
+        run: npm dist-tag --otp=${TOTP_CODE} add "${NPM_PACKAGE}@${VERSION}" stack_release
+      - name: Create Draft Release Notes
+        run: >-
+          gh release create "${VERSION}"
+          --title="${VERSION}"
+          --generate-notes
+          --draft
+      - if: always()
+        uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current
+        with:
+          vaultUrl: ${{ secrets.VAULT_ADDR }}
+          vaultRoleId: ${{ secrets.VAULT_ROLE_ID }}
+          vaultSecretId: ${{ secrets.VAULT_SECRET_ID }}
+          slackChannel: "#synthetics-user_experience-uptime"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c9a5383..d4179c6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -10,13 +10,11 @@ repos:
     -   id: check-yaml
     -   id: end-of-file-fixer
 
--   repo: git@github.com:elastic/apm-pipeline-library
+-   repo: https://github.com/elastic/apm-pipeline-library
     rev: current
     hooks:
     -   id: check-bash-syntax
     -   id: check-en-dashes
-    -   id: check-jenkins-pipelines
-    -   id: check-jjbb
     -   id: check-unicode-non-breaking-spaces
     -   id: remove-en-dashes
     -   id: remove-unicode-non-breaking-spaces
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4ff2e17..9a043d8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -146,17 +146,11 @@ We run end to end tests for the Synthetics agent itself, as well as the Elastic
 
 The release process is also automated in the way any specific commit from the main branch can be potentially released, for such it's required the below steps:
 
-1. Login to apm-ci.elastic.co
-1. Go to the [main](https://apm-ci.elastic.co/job/apm-agent-rum/job/elastic-synthetics/job/main/) pipeline.
-1. Click on `Build with parameters` with the below checkbox:
-
-- `release` to be selected.
-- other checkboxes should be left as default.
-
-1. Click on `Build`.
-1. Wait for an email or slack message to confirm the release is ready to be approved, it might take roughly 20 minutes.
-1. Click on the URL from the email or slack.
-1. Click on approve or abort.
+1. Go to https://github.com/elastic/synthetics/actions/workflows/release.yml
+1. Click the `Run workflow` dropdown button.
+1. Use the `main` branch option which is selected by default.
+1. Choose whether a `stack_release` dist-tag should be created or not.
+1. Click the green `Run workflow` button to trigger the release workflow.
 1. Then you can go to the `https://www.npmjs.com/package/@elastic/synthetics` to validate that the bundles have been published.
 
 ## CI
diff --git a/README.md b/README.md
index 9343fad..8ac68c1 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-[![Build Status](https://apm-ci.elastic.co/job/apm-agent-rum/job/elastic-synthetics/job/main/badge/icon)](https://apm-ci.elastic.co/job/apm-agent-rum/job/elastic-synthetics/job/main/)
+[![ci](https://github.com/elastic/synthetics/actions/workflows/ci.yml/badge.svg)](https://github.com/elastic/synthetics/actions/workflows/ci.yml)
+[![e2e](https://github.com/elastic/synthetics/actions/workflows/e2e.yml/badge.svg)](https://github.com/elastic/synthetics/actions/workflows/e2e.yml)
 
 # Experimental Synthetics Agent
 
diff --git a/THROTTLING.md b/THROTTLING.md
new file mode 100644
index 0000000..566b2a8
--- /dev/null
+++ b/THROTTLING.md
@@ -0,0 +1,1 @@
+⚠️ Placeholder for throttling details in Elastic's global managed testing infrastructure (the service) and for Private Locations
diff --git a/package-lock.json b/package-lock.json
index 25b2b82..67a5121 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,17 @@
 {
   "name": "@elastic/synthetics",
-  "version": "1.0.0-beta.44",
+  "version": "1.0.0-beta.45",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@elastic/synthetics",
-      "version": "1.0.0-beta.44",
+      "version": "1.0.0-beta.45",
       "license": "MIT",
       "dependencies": {
         "archiver": "^5.3.1",
-        "commander": "^9.4.1",
-        "deepmerge": "^4.2.2",
+        "commander": "^10.0.1",
+        "deepmerge": "^4.3.1",
         "enquirer": "^2.3.6",
         "esbuild": "^0.16.10",
         "expect": "^28.1.3",
@@ -21,14 +21,14 @@
         "pirates": "^4.0.5",
         "playwright-chromium": "=1.32.3",
         "playwright-core": "=1.32.3",
-        "semver": "^7.3.8",
-        "sharp": "^0.31.1",
+        "semver": "^7.5.0",
+        "sharp": "^0.32.0",
         "snakecase-keys": "^4.0.1",
-        "sonic-boom": "^3.2.0",
+        "sonic-boom": "^3.3.0",
         "source-map-support": "^0.5.21",
         "typescript": "^4.8.4",
         "undici": "^5.21.2",
-        "yaml": "^2.1.3"
+        "yaml": "^2.2.2"
       },
       "bin": {
         "elastic-synthetics": "dist/cli.js",
@@ -2346,11 +2346,11 @@
       }
     },
     "node_modules/commander": {
-      "version": "9.4.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz",
-      "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==",
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+      "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
       "engines": {
-        "node": "^12.20.0 || >=14"
+        "node": ">=14"
       }
     },
     "node_modules/compare-versions": {
@@ -2544,9 +2544,9 @@
       "dev": true
     },
     "node_modules/deepmerge": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
-      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
       "engines": {
         "node": ">=0.10.0"
       }
@@ -4973,9 +4973,9 @@
       }
     },
     "node_modules/node-addon-api": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz",
-      "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA=="
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
+      "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
     },
     "node_modules/node-int64": {
       "version": "0.4.0",
@@ -5615,9 +5615,9 @@
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "node_modules/semver": {
-      "version": "7.3.8",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-      "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
+      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
       "dependencies": {
         "lru-cache": "^6.0.0"
       },
@@ -5653,16 +5653,16 @@
       "dev": true
     },
     "node_modules/sharp": {
-      "version": "0.31.1",
-      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz",
-      "integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==",
+      "version": "0.32.0",
+      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.0.tgz",
+      "integrity": "sha512-yLAypVcqj1toSAqRSwbs86nEzfyZVDYqjuUX8grhFpeij0DDNagKJXELS/auegDBRDg1XBtELdOGfo2X1cCpeA==",
       "hasInstallScript": true,
       "dependencies": {
         "color": "^4.2.3",
         "detect-libc": "^2.0.1",
-        "node-addon-api": "^5.0.0",
+        "node-addon-api": "^6.0.0",
         "prebuild-install": "^7.1.1",
-        "semver": "^7.3.7",
+        "semver": "^7.3.8",
         "simple-get": "^4.0.1",
         "tar-fs": "^2.1.1",
         "tunnel-agent": "^0.6.0"
@@ -5801,9 +5801,9 @@
       }
     },
     "node_modules/sonic-boom": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.0.tgz",
-      "integrity": "sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz",
+      "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==",
       "dependencies": {
         "atomic-sleep": "^1.0.0"
       }
@@ -6471,9 +6471,9 @@
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
     },
     "node_modules/yaml": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
-      "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
+      "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
       "engines": {
         "node": ">= 14"
       }
@@ -8287,9 +8287,9 @@
       }
     },
     "commander": {
-      "version": "9.4.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz",
-      "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw=="
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+      "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="
     },
     "compare-versions": {
       "version": "3.6.0",
@@ -8444,9 +8444,9 @@
       "dev": true
     },
     "deepmerge": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
-      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
     },
     "detect-libc": {
       "version": "2.0.1",
@@ -10271,9 +10271,9 @@
       }
     },
     "node-addon-api": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz",
-      "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA=="
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
+      "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
     },
     "node-int64": {
       "version": "0.4.0",
@@ -10717,9 +10717,9 @@
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
     "semver": {
-      "version": "7.3.8",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-      "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
+      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
       "requires": {
         "lru-cache": "^6.0.0"
       }
@@ -10743,15 +10743,15 @@
       "dev": true
     },
     "sharp": {
-      "version": "0.31.1",
-      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz",
-      "integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==",
+      "version": "0.32.0",
+      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.0.tgz",
+      "integrity": "sha512-yLAypVcqj1toSAqRSwbs86nEzfyZVDYqjuUX8grhFpeij0DDNagKJXELS/auegDBRDg1XBtELdOGfo2X1cCpeA==",
       "requires": {
         "color": "^4.2.3",
         "detect-libc": "^2.0.1",
-        "node-addon-api": "^5.0.0",
+        "node-addon-api": "^6.0.0",
         "prebuild-install": "^7.1.1",
-        "semver": "^7.3.7",
+        "semver": "^7.3.8",
         "simple-get": "^4.0.1",
         "tar-fs": "^2.1.1",
         "tunnel-agent": "^0.6.0"
@@ -10840,9 +10840,9 @@
       }
     },
     "sonic-boom": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.0.tgz",
-      "integrity": "sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz",
+      "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==",
       "requires": {
         "atomic-sleep": "^1.0.0"
       }
@@ -11337,9 +11337,9 @@
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
     },
     "yaml": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
-      "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg=="
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
+      "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="
     },
     "yargs": {
       "version": "17.5.1",
diff --git a/package.json b/package.json
index 0ae9e0b..c80c0bd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@elastic/synthetics",
-  "version": "1.0.0-beta.44",
+  "version": "1.0.0-beta.45",
   "description": "Elastic synthetic monitoring agent",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
@@ -41,8 +41,8 @@
   "license": "MIT",
   "dependencies": {
     "archiver": "^5.3.1",
-    "commander": "^9.4.1",
-    "deepmerge": "^4.2.2",
+    "commander": "^10.0.1",
+    "deepmerge": "^4.3.1",
     "enquirer": "^2.3.6",
     "esbuild": "^0.16.10",
     "expect": "^28.1.3",
@@ -52,14 +52,14 @@
     "pirates": "^4.0.5",
     "playwright-chromium": "=1.32.3",
     "playwright-core": "=1.32.3",
-    "semver": "^7.3.8",
-    "sharp": "^0.31.1",
+    "semver": "^7.5.0",
+    "sharp": "^0.32.0",
     "snakecase-keys": "^4.0.1",
-    "sonic-boom": "^3.2.0",
+    "sonic-boom": "^3.3.0",
     "source-map-support": "^0.5.21",
     "typescript": "^4.8.4",
     "undici": "^5.21.2",
-    "yaml": "^2.1.3"
+    "yaml": "^2.2.2"
   },
   "devDependencies": {
     "@types/jest": "^28.1.8",
diff --git a/src/plugins/network.ts b/src/plugins/network.ts
index c7a1b1d..ccfb279 100644
--- a/src/plugins/network.ts
+++ b/src/plugins/network.ts
@@ -38,6 +38,22 @@ function epochTimeInSeconds() {
   return getTimestamp() / 1e6;
 }
 
+/**
+ * Find the first positive number in an array for Resource timing data
+ */
+const firstPositive = (numbers: number[]) => {
+  for (let i = 0; i < numbers.length; ++i) {
+    if (numbers[i] > 0) {
+      return numbers[i];
+    }
+  }
+  return null;
+};
+
+const roundMilliSecs = (value: number): number => {
+  return Math.floor(value * 1000) / 1000;
+};
+
 /**
  * Used as a key in each Network Request to identify the
  * associated request across distinct lifecycle events
@@ -83,7 +99,6 @@ export class NetworkManager {
     const { product } = await client.send('Browser.getVersion');
     const [name, version] = product.split('/');
     this._browser = { name, version };
-    await client.send('Network.enable');
     /**
      * Listen for all network events from PW context
      */
@@ -135,7 +150,16 @@ export class NetworkManager {
       responseReceivedTime: -1,
       resourceSize: 0,
       transferSize: 0,
-      timings: null,
+      timings: {
+        blocked: -1,
+        dns: -1,
+        ssl: -1,
+        connect: -1,
+        send: -1,
+        wait: -1,
+        receive: -1,
+        total: -1,
+      },
     };
 
     if (request.redirectedFrom()) {
@@ -162,6 +186,46 @@ export class NetworkManager {
       redirectURL: networkEntry.response.redirectURL,
     };
 
+    // Gather all resource timing information up until the
+    // TTFB(Time to first byte) is received
+    const timing = request.timing();
+    const blocked =
+      roundMilliSecs(
+        firstPositive([
+          timing.domainLookupStart,
+          timing.connectStart,
+          timing.requestStart,
+        ])
+      ) || -1;
+    const dns =
+      timing.domainLookupEnd !== -1
+        ? roundMilliSecs(timing.domainLookupEnd - timing.domainLookupStart)
+        : -1;
+    const connect =
+      timing.connectEnd !== -1
+        ? roundMilliSecs(timing.connectEnd - timing.connectStart)
+        : -1;
+    const ssl =
+      timing.secureConnectionStart !== -1
+        ? roundMilliSecs(timing.connectEnd - timing.secureConnectionStart)
+        : -1;
+    const wait =
+      timing.responseStart !== -1
+        ? roundMilliSecs(timing.responseStart - timing.requestStart)
+        : -1;
+
+    networkEntry.timings = {
+      blocked,
+      dns,
+      ssl,
+      connect,
+      send: 0, // not exposed via RT api
+      wait,
+      receive: -1, // will be available after full response is received
+      total: -1,
+    };
+    this._calcTotalTime(networkEntry, timing);
+
     const page = request.frame().page();
     this._addBarrier(
       page,
@@ -200,83 +264,21 @@ export class NetworkManager {
     if (!networkEntry) return;
 
     networkEntry.loadEndTime = epochTimeInSeconds();
+    // responseEnd is fired after the last byte of the response is received.
     const timing = request.timing();
-    const { loadEndTime, requestSentTime } = networkEntry;
-    networkEntry.timings = {
-      blocked: -1,
-      dns: -1,
-      ssl: -1,
-      connect: -1,
-      send: -1,
-      wait: -1,
-      receive: -1,
-      total: -1,
-    };
-
-    const firstPositive = (numbers: number[]) => {
-      for (let i = 0; i < numbers.length; ++i) {
-        if (numbers[i] > 0) {
-          return numbers[i];
-        }
-      }
-      return null;
-    };
-    const roundMilliSecs = (value: number): number => {
-      return Math.floor(value * 1000) / 1000;
-    };
-
-    if (timing.startTime === 0) {
-      // Convert to milliseconds before round off
-      const total = roundMilliSecs((loadEndTime - requestSentTime) * 1000);
-      networkEntry.timings.total = total;
-      return;
-    }
-
-    const blocked =
-      roundMilliSecs(
-        firstPositive([
-          timing.domainLookupStart,
-          timing.connectStart,
-          timing.requestStart,
-        ])
-      ) || -1;
-    const dns =
-      timing.domainLookupEnd !== -1
-        ? roundMilliSecs(timing.domainLookupEnd - timing.domainLookupStart)
-        : -1;
-    const connect =
-      timing.connectEnd !== -1
-        ? roundMilliSecs(timing.connectEnd - timing.connectStart)
-        : -1;
-    const ssl =
-      timing.secureConnectionStart !== -1
-        ? roundMilliSecs(timing.connectEnd - timing.secureConnectionStart)
-        : -1;
-    const wait =
-      timing.responseStart !== -1
-        ? roundMilliSecs(timing.responseStart - timing.requestStart)
-        : -1;
     const receive =
       timing.responseEnd !== -1
         ? roundMilliSecs(timing.responseEnd - timing.responseStart)
         : -1;
-    const total = [blocked, dns, connect, wait, receive].reduce(
-      (pre, cur) => (cur > 0 ? cur + pre : pre),
-      0
-    );
-    networkEntry.timings = {
-      blocked,
-      dns,
-      connect,
-      ssl,
-      wait,
-      send: 0, // not exposed via RT api
-      receive,
-      total,
-    };
+    networkEntry.timings.receive = receive;
+    this._calcTotalTime(networkEntry, timing);
 
-    const page = request.frame().page();
     // For aborted/failed requests sizes will not be present
+    if (timing.startTime <= 0) {
+      return;
+    }
+
+    const page = request.frame().page();
     this._addBarrier(
       page,
       request.sizes().then(sizes => {
@@ -295,6 +297,35 @@ export class NetworkManager {
     );
   }
 
+  /**
+   * Calculates the total time for the network request based on the ResourceTiming
+   * data from Playwright. Fallbacks to the event timings if ResourceTiming data
+   * is not available.
+   */
+  private _calcTotalTime(
+    entry: NetworkInfo,
+    rtiming: ReturnType<Request['timing']>
+  ) {
+    const timings = entry.timings;
+    entry.timings.total = [
+      timings.blocked,
+      timings.dns,
+      timings.connect,
+      timings.wait,
+      timings.receive,
+    ].reduce((pre, cur) => ((cur || -1) > 0 ? cur + pre : pre), 0);
+
+    // fallback when ResourceTiming data is not available
+    if (rtiming.startTime <= 0) {
+      const end =
+        entry.loadEndTime ||
+        entry.responseReceivedTime ||
+        entry.requestSentTime;
+      const total = roundMilliSecs((end - entry.requestSentTime) * 1000);
+      entry.timings.total = total <= 0 ? -1 : total;
+    }
+  }
+
   async stop() {
     /**
      * Waiting for all network events is error prone and might hang the tests


Test patch:
diff --git a/__tests__/plugins/network.test.ts b/__tests__/plugins/network.test.ts
index f1281fe..134502c 100644
--- a/__tests__/plugins/network.test.ts
+++ b/__tests__/plugins/network.test.ts
@@ -287,6 +287,36 @@ describe('network', () => {
     });
   });
 
+  it('timings for broken response', async () => {
+    const driver = await Gatherer.setupDriver({
+      wsEndpoint,
+    });
+    const network = new NetworkManager(driver);
+    await network.start();
+
+    server.route('/index', async (_, res) => {
+      res.setHeader('content-type', 'text/html');
+      await delay(10);
+      res.write(`<head></head>`);
+      await delay(500);
+      res.end(`<body></body>`);
+    });
+
+    await driver.page
+      .goto(server.PREFIX + '/index', { timeout: 100 })
+      .catch(() => {});
+
+    const netinfo = await network.stop();
+    await Gatherer.stop();
+
+    expect(netinfo.length).toBe(1);
+    // responseEnd is never fired as we are not waiting for the
+    // `responseFinished` event instead cutting off after TTFB
+    expect(netinfo[0].timings.receive).toEqual(-1);
+    expect(netinfo[0].timings.total).toBeGreaterThan(10);
+    expect(netinfo[0].timings.total).toBeLessThan(500);
+  });
+
   it("doesn't capture network info from request context", async () => {
     const driver = await Gatherer.setupDriver({
       wsEndpoint,