Repository: elastic/synthetics (ts)

Issue:
Add option for JPEG screenshots
For those who are space conscious JPEG screenshots would be welcome as a CLI option. We would need to add a corresponding heartbeat option. 

Pull request:
feat: use jpeg for screenshots
+ fix #233 
+ Defaults to jpeg images with quality 80(default for jpeg in playwright) for screenshots, Size is 2x smaller than PNG for todos example 
```
28060 vs 14936 kB
```

**Note: Verified that Kibana Uptime UI works even without the proper mime type being set**


Patch:
diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile
index 7d58227..92c6854 100644
--- a/.ci/Jenkinsfile
+++ b/.ci/Jenkinsfile
@@ -12,8 +12,6 @@ pipeline {
     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"
-    DOCKER_IMG = "${env.DOCKER_REGISTRY}/observability-ci/synthetics"
-    DOCKER_IMG_PUBLIC = "${env.DOCKER_REGISTRY}/experimental/synthetics"
     NPMRC_SECRET = 'secret/jenkins-ci/npmjs/elasticmachine'
     TOTP_SECRET = 'totp/code/npmjs-elasticmachine'
     SLACK_CHANNEL = '#synthetics-user_experience-uptime'
@@ -121,11 +119,12 @@ pipeline {
       }
     }
     stage('E2e Test') {
+      agent { label 'ubuntu-18 && immutable' }
       environment {
         E2E_FOLDER = "__tests__/e2e"
       }
       options {
-        timeout(15)
+        timeout(20)
       }
       steps {
         withGithubNotify(context: 'E2e Test') {
@@ -149,62 +148,6 @@ pipeline {
         }
       }
     }
-    /**
-     Publish Docker images.
-     */
-    stage('Publish Docker image'){
-      options {
-        timeout(15)
-      }
-      matrix {
-        agent { label 'ubuntu-18 && immutable' }
-        axes {
-          axis {
-            name 'STACK_VERSION'
-            values '8.0.0-synthetics', '7.10.0-synthetics', '7.11.0-synthetics'
-          }
-        }
-        stages {
-          stage('package'){
-            steps {
-              withGithubNotify(context: 'Package') {
-                cleanup()
-                withNodeEnv(){
-                  dir("${BASE_DIR}"){
-                    sh(label: 'Build',script: 'npm run build')
-                    sh(label: 'Pack',script: 'npm pack')
-                  }
-                }
-              }
-            }
-          }
-          stage('observability-ci'){
-            environment {
-              DOCKER_IMG_TAG = "${env.DOCKER_IMG}:${env.GIT_BASE_COMMIT}-${env.STACK_VERSION}"
-              DOCKER_IMG_TAG_BRANCH = "${env.DOCKER_IMG}:${env.BRANCH_NAME}-${env.STACK_VERSION}"
-            }
-            steps {
-              withGithubNotify(context: "push ${DOCKER_IMG_TAG_BRANCH}") {
-                pushDockerImage()
-              }
-            }
-          }
-          stage('experimental'){
-            when {
-              branch 'master'
-            }
-            environment {
-              DOCKER_IMG_TAG_BRANCH = "${env.DOCKER_IMG_PUBLIC}:${env.STACK_VERSION}"
-            }
-            steps {
-              withGithubNotify(context: "push ${DOCKER_IMG_TAG_BRANCH}") {
-                pushDockerImage()
-              }
-            }
-          }
-        }
-      }
-    }
     stage('Release') {
       options {
         skipDefaultCheckout()
@@ -318,31 +261,6 @@ def withNodeEnv(Map args=[:], Closure body){
   }
 }
 
-def pushDockerImage(){
-  dir("${BASE_DIR}"){
-    dockerLogin(secret: "${env.DOCKERELASTIC_SECRET}",
-      registry: "${env.DOCKER_REGISTRY}")
-    sh(label: 'Build Docker image',
-      script: """docker build \
-        -t ${env.DOCKER_IMG_TAG} \
-        -t ${env.DOCKER_IMG_TAG_BRANCH} \
-        --build-arg STACK_VERSION=${env.STACK_VERSION} \
-        --label BRANCH_NAME=${env.BRANCH_NAME} \
-        --label GIT_SHA=${env.GIT_BASE_COMMIT} \
-        --label TIMESTAMP=\$(date +%Y-%m-%d_%H:%M) \
-        .
-    """)
-    retryWithSleep(retries: 3, seconds: 5, backoff: true){
-      if(env.DOCKER_IMG_TAG){
-        sh(label: 'Push Docker image sha',
-          script: "docker push ${env.DOCKER_IMG_TAG}")
-      }
-      sh(label: 'Push Docker image name',
-        script: "docker push ${env.DOCKER_IMG_TAG_BRANCH}")
-    }
-  }
-}
-
 /**
   This is the wrapper to send notifications for the release process through
   slack and email, since it requires some formatting to support the same
diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index 5064824..0000000
--- a/.dockerignore
+++ /dev/null
@@ -1,106 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# TypeScript v1 declaration files
-typings/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variables file
-.env
-.env.test
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-
-# Next.js build output
-.next
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and *not* Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# TernJS port file
-.tern-port
-
-tmp
-
-# Vim temp files
-*.swp
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ff709d..8582ccc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
 
 All notable changes to this project will be documented in this file.
 
+## v0.0.1-alpha14 (2021-04-14)
+
+### Features
+
+- Add support for custom reporters for the Synthetics runner
+  [#254](https://github.com/elastic/synthetics/issues/254)
+
+## v0.0.1-alpha13 (2021-04-08)
+
+### Features
+
+- Remove duplicate payload fields and keep the network fields under ECS
+  [#252](https://github.com/elastic/synthetics/issues/252)
+
+### Bug fixes
+
+- Measure the network timings for aborted and inflight network requests
+  correctly[#251](https://github.com/elastic/synthetics/pull/251)
+
 ## v0.0.1-alpha12 (2021-03-31)
 
 ### Features
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 839788a..bca043a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -129,8 +129,8 @@ integration. Follow the below steps to invoke the synthetics agent using
 heartbeat locally
 
 1. `cd /examples/docker`
-2. `./pull-latest.sh` - Pulls the latest heartbeat images
-3. `./run-build-local.sh $HEARTBEAT_ARGS` - Runs the locally built heartbeat image with the
+2. `./pull-latest.sh` - Pulls the latest heartbeat image
+3. `./run.sh $HEARTBEAT_ARGS` - Run the heartbeat image with the
    synthetic tests configured in `heartbeat.docker.yml` file.
 
 ### Releasing
@@ -155,8 +155,10 @@ The release process is also automated in the way any specific commit from the ma
 1. Login to apm-ci.elastic.co
 1. Go to the [master](https://apm-ci.elastic.co/job/apm-agent-rum/job/elastic-synthetics/job/master/) pipeline.
 1. Click on `Build with parameters` with the below checkbox:
-  * `release` to be selected.
-  * other checkboxes should be left as default.
+
+- `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.
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index bc3f6cf..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,35 +0,0 @@
-ARG STACK_VERSION=7.10.0-synthetics
-FROM docker.elastic.co/observability-ci/heartbeat:${STACK_VERSION}
-ENV ELASTIC_SYNTHETICS_CAPABLE=true
-USER root
-RUN yum -y install epel-release && \
-    yum -y install atk cups gtk gdk xrandr pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 \
-	  libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 \
-	  alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils \
-	  xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc \
-	  yum clean all && \
-    rm -rf /var/cache/yum
-
-ENV TINI_VERSION v0.19.0
-ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
-RUN chmod +x /tini
-ENTRYPOINT ["/tini", "--", "/usr/local/bin/docker-entrypoint"]
-
-ENV SUITES_DIR=/usr/share/heartbeat/suites
-
-# or docker run your-image /your/program ...
-RUN echo /usr/share/heartbeat/.node \
-      /usr/share/heartbeat/.npm \
-      /usr/share/heartbeat/.cache \
-      /usr/share/heartbeat/.config \
-      $SUITES_DIR \
-      /opt/elastic-synthetics | xargs -IDIR sh -c "mkdir DIR && chown -R heartbeat:heartbeat DIR"
-ENV NODE_PATH=/usr/share/heartbeat/.node
-USER heartbeat
-RUN  cd /usr/share/heartbeat/.node \
-      && mkdir node \
-      && curl https://nodejs.org/dist/v12.18.4/node-v12.18.4-linux-x64.tar.xz | tar -xJ --strip 1 -C node
-ENV PATH="/usr/share/heartbeat/.node/node/bin:$PATH"
-# Install the latest version of @elastic/synthetics forcefully ignoring the previously
-# cached node_modules, hearbeat then calls the global executable to run test suites
-RUN npm i -g -f @elastic/synthetics
diff --git a/build-docker.sh b/build-docker.sh
deleted file mode 100755
index 738fe73..0000000
--- a/build-docker.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-set -e
-set -x
-STACK_VERSION=${1:-7.10.0}
-echo "Installing package dependencies for running local build"
-npm i
-echo "Building docker image based on ${STACK_VERSION}..."
-docker build . -t heartbeat-synthetics-local --build-arg STACK_VERSION=$STACK_VERSION-synthetics
diff --git a/examples/docker/bash-build-local.sh b/examples/docker/bash-build-local.sh
deleted file mode 100755
index d033de5..0000000
--- a/examples/docker/bash-build-local.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-# Build a local docker image using the latest source in this repo
-# and run a bash prompty on the container
-pushd ../../
-./build-docker.sh $1
-popd
-./bash.sh heartbeat-synthetics-local
diff --git a/examples/docker/bash.sh b/examples/docker/bash.sh
index df95454..7534e47 100755
--- a/examples/docker/bash.sh
+++ b/examples/docker/bash.sh
@@ -1,10 +1,6 @@
 #!/usr/bin/env bash
-VERSION=${1:-7.10.0}
-if [[ ${VERSION} =~ ^[0-9] ]]; then
-	IMAGE="docker.elastic.co/experimental/synthetics:${VERSION}-synthetics"
-else
-	IMAGE=${VERSION}
-fi
+VERSION=${1:-8.0.0-SNAPSHOT}
+IMAGE="docker.elastic.co/beats/heartbeat:${VERSION}"
 echo "Using image ${IMAGE}"
 docker run \
   -it \
diff --git a/examples/docker/heartbeat.docker.yml b/examples/docker/heartbeat.docker.yml
index 4b5052c..5224bd6 100644
--- a/examples/docker/heartbeat.docker.yml
+++ b/examples/docker/heartbeat.docker.yml
@@ -1,26 +1,22 @@
 ---
-seccomp.enabled: false
-heartbeat.config.monitors:
-  path: "${path.config}/monitors.d/*.yml"
-  reload.enabled: false
-  reload.period: 5s
-
-heartbeat.synthetic_suites:
-- name: Todos
-  path: "/opt/elastic-synthetics/examples/todos"
-  schedule: "@every 1m"
-
-processors:
-
 heartbeat.monitors:
-- type: browser
-  id: my-monitor
-  name: My Monitor
-  schedule: "@every 1m"
-  script: |-
-    step("load homepage", async () => {
-        await page.goto('https://www.elastic.co');
-    });
-    step("hover over products menu", async () => {
-        await page.hover('css=[data-nav-item=products]');
-    });
+  - type: browser
+    id: my-monitor
+    name: My Monitor
+    schedule: '@every 1m'
+    source:
+      inline:
+        script: |-
+          step("load homepage", async () => {
+              await page.goto('https://www.elastic.co');
+          });
+          step("hover over products menu", async () => {
+              await page.hover('css=[data-nav-item=products]');
+          });
+  - type: browser
+    id: todos
+    name: Todos
+    schedule: '@every 1m'
+    source:
+      local:
+        path: '/opt/elastic-synthetics/examples/todos'
diff --git a/examples/docker/run-build-local.sh b/examples/docker/run-build-local.sh
deleted file mode 100755
index 8838f28..0000000
--- a/examples/docker/run-build-local.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-# Build a local docker image using the latest source in this repo
-# and run heartbeat with the config in this directory
-pushd ../../
-./build-docker.sh "${1}"
-popd || exit
-./run.sh heartbeat-synthetics-local "${@:2}"
diff --git a/examples/docker/run.sh b/examples/docker/run.sh
index a5fa945..a04fa21 100755
--- a/examples/docker/run.sh
+++ b/examples/docker/run.sh
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 set -ex
-VERSION=${1:-7.10.0}
+VERSION=${1:-8.0.0-SNAPSHOT}
 if [ -n "${1}" ]; then
   shift # discard first arg
 fi
@@ -11,13 +11,7 @@ else
   HEARTBEAT_ARGS="$*"
 fi
 
-# Set Image based on version
-if [[ ${VERSION} =~ ^[0-9] ]]; then
-	IMAGE="docker.elastic.co/experimental/synthetics:${VERSION}-synthetics"
-else
-	IMAGE=${VERSION}
-fi
-
+IMAGE="docker.elastic.co/beats/heartbeat:${VERSION}"
 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 
 echo "Using image '${IMAGE}' with extra args: ${HEARTBEAT_ARGS}"
@@ -28,7 +22,7 @@ docker run \
   --net=host \
   --security-opt seccomp="${SCRIPT_DIR}"/seccomp_profile.json \
   --volume="$(pwd)/heartbeat.docker.yml:/usr/share/heartbeat/heartbeat.yml:ro" \
-  --volume="$(pwd)/../../:/opt/elastic-synthetics:rw" \
+  --volume="$(pwd)/../../:/opt/elastic-synthetics:ro" \
   "${IMAGE}" \
   --strict.perms=false -e \
   ${HEARTBEAT_ARGS}
diff --git a/examples/todos/Dockerfile b/examples/todos/Dockerfile
index 4b3c802..3261989 100644
--- a/examples/todos/Dockerfile
+++ b/examples/todos/Dockerfile
@@ -1,13 +1,10 @@
-# This Dockerfile illustrates the usage of synthetics in an air gapped environment, where 
+# This Dockerfile illustrates the usage of synthetics in an air gapped environment, where
 # public internet access is not available. In this situation you'll want to create
 # a custom image using our official docker image as a base.
 
 # Use our synthetics image as a base.
 ARG STACK_VERSION=latest
-FROM docker.elastic.co/experimental/synthetics:${STACK_VERSION}-synthetics
-# Use the line below if you're using a custom base image, usually only for
-# developers
-#FROM heartbeat-synthetics-local
+FROM docker.elastic.co/beats/heartbeat:${STACK_VERSION}
 
 # This flag variable will prevent heartbeat from running `npm i` or
 # similar commands that depend on an internet connection.
diff --git a/examples/todos/build-offline-dockerfile.sh b/examples/todos/build-offline-dockerfile.sh
index 25b57e4..0d3823a 100755
--- a/examples/todos/build-offline-dockerfile.sh
+++ b/examples/todos/build-offline-dockerfile.sh
@@ -4,4 +4,4 @@
 
 # You'll want to run this in an environment with internet access so that NPM deps can be installed,
 # then, take the resultant image and transfer that to your air gapped network.
-docker build --build-arg STACK_VERSION=7.10.0 -t my-custom-heartbeat .
+docker build --build-arg STACK_VERSION=7.12.0 -t my-custom-heartbeat .
diff --git a/examples/todos/heartbeat.docker.yml b/examples/todos/heartbeat.docker.yml
index 48c52f9..e8fb346 100644
--- a/examples/todos/heartbeat.docker.yml
+++ b/examples/todos/heartbeat.docker.yml
@@ -1,12 +1,16 @@
 ---
 seccomp.enabled: false
 heartbeat.config.monitors:
-  path: "${path.config}/monitors.d/*.yml"
+  path: '${path.config}/monitors.d/*.yml'
   reload.enabled: false
   reload.period: 5s
 
-heartbeat.synthetic_suites:
-- name: Todos
+heartbeat.monitors:
   # SUITES_DIR is an environment var provided by the docker container
-  path: "${SUITES_DIR}/todos"
-  schedule: "@every 1m"
\ No newline at end of file
+  - type: browser
+    id: todos
+    name: Todos
+    schedule: '@every 1m'
+    source:
+      local:
+        path: '${SUITES_DIR}/todos'
diff --git a/examples/todos/package.json b/examples/todos/package.json
index bc42984..a5aa8c2 100644
--- a/examples/todos/package.json
+++ b/examples/todos/package.json
@@ -5,6 +5,6 @@
   "scripts": {},
   "license": "MIT",
   "dependencies": {
-    "@elastic/synthetics": "*"
+    "@elastic/synthetics": "^0.0.1-alpha || ^1.0.0-beta"
   }
 }
diff --git a/package-lock.json b/package-lock.json
index 60ba74a..e2fec36 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4780,9 +4780,9 @@
       }
     },
     "playwright-chromium": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.8.0.tgz",
-      "integrity": "sha512-DMyWmFPXkW0Tm5yBx2pV3m5F8qBSk29+DuZd21liwnG8qNZX43zEY1QpsS51zWPYFgi+nHkRr343GycyAoSUzQ==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.10.0.tgz",
+      "integrity": "sha512-ry/60/YKGJfrnaS7j5C3+xmA2DAMv6pBE351FoTHe4Llq0WU92IL0rBYK3TkK3PzSHYtp1KdCRsRjDnJhImA3g==",
       "requires": {
         "commander": "^6.1.0",
         "debug": "^4.1.1",
@@ -4795,6 +4795,7 @@
         "proper-lockfile": "^4.1.1",
         "proxy-from-env": "^1.1.0",
         "rimraf": "^3.0.2",
+        "stack-utils": "^2.0.3",
         "ws": "^7.3.1"
       },
       "dependencies": {
@@ -5801,7 +5802,6 @@
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz",
       "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==",
-      "dev": true,
       "requires": {
         "escape-string-regexp": "^2.0.0"
       },
@@ -5809,8 +5809,7 @@
         "escape-string-regexp": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
-          "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
-          "dev": true
+          "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
         }
       }
     },
diff --git a/package.json b/package.json
index fbbc689..9f8b142 100644
--- a/package.json
+++ b/package.json
@@ -14,8 +14,7 @@
     "test": "npm run test:unit && npm run test:browser-service",
     "test:unit": "jest",
     "test:browser-service": "WSENDPOINT=ws://localhost:9322 npm run test:unit",
-    "coverage": "jest --coverage",
-    "docker": "./build-docker.sh"
+    "coverage": "jest --coverage"
   },
   "bin": {
     "@elastic/synthetics": "dist/cli.js",
@@ -42,7 +41,7 @@
     "commander": "^7.0.0",
     "http-proxy": "^1.18.1",
     "kleur": "^4.1.3",
-    "playwright-chromium": "=1.8.0",
+    "playwright-chromium": "=1.10.0",
     "snakecase-keys": "^3.2.1",
     "sonic-boom": "^1.3.0",
     "source-map-support": "^0.5.19",
diff --git a/src/core/runner.ts b/src/core/runner.ts
index bfd338f..062b2ab 100644
--- a/src/core/runner.ts
+++ b/src/core/runner.ts
@@ -180,6 +180,18 @@ export default class Runner {
     log(`Runner: start step (${step.name})`);
     const { metrics, screenshots } = options;
     const { driver, pluginManager } = context;
+    /**
+     * URL needs to be the first navigation request of any step
+     * Listening for request solves the case where `about:blank` would be
+     * reported for failed navigations
+     */
+    const captureUrl = req => {
+      if (!data.url && req.isNavigationRequest()) {
+        data.url = req.url();
+      }
+      driver.page.off('request', captureUrl);
+    };
+    driver.page.on('request', captureUrl);
     try {
       pluginManager.onStep(step);
       await step.callback();
@@ -190,10 +202,15 @@ export default class Runner {
       data.status = 'failed';
       data.error = error;
     } finally {
-      data.url = driver.page.url();
+      data.url ??= driver.page.url();
       if (screenshots) {
         await driver.page.waitForLoadState('load');
-        data.screenshot = (await driver.page.screenshot()).toString('base64');
+        data.screenshot = (
+          await driver.page.screenshot({
+            type: 'jpeg',
+            quality: 80,
+          })
+        ).toString('base64');
       }
     }
     log(`Runner: end step (${step.name})`);
diff --git a/src/reporters/json.ts b/src/reporters/json.ts
index 32462a1..72473f4 100644
--- a/src/reporters/json.ts
+++ b/src/reporters/json.ts
@@ -68,6 +68,7 @@ type OutputFields = {
   root_fields?: Record<string, unknown>;
   payload?: Payload | NetworkInfo;
   blob?: string;
+  blob_mime?: string;
 };
 
 function getMetadata() {
@@ -268,6 +269,7 @@ export default class JSONReporter extends BaseReporter {
             journey,
             step,
             blob: screenshot,
+            blob_mime: 'image/jpeg',
           });
         }
         this.writeJSON({
@@ -369,6 +371,7 @@ export default class JSONReporter extends BaseReporter {
     error,
     payload,
     blob,
+    blob_mime,
     url,
   }: OutputFields) {
     this.write({
@@ -379,6 +382,7 @@ export default class JSONReporter extends BaseReporter {
       root_fields: { ...(root_fields || {}), ...getMetadata() },
       payload,
       blob,
+      blob_mime,
       error: formatError(error),
       url,
       package_version: version,


Test patch:
diff --git a/__tests__/core/runner.test.ts b/__tests__/core/runner.test.ts
index 49ce1ff..ed9bfb0 100644
--- a/__tests__/core/runner.test.ts
+++ b/__tests__/core/runner.test.ts
@@ -189,6 +189,45 @@ describe('runner', () => {
     ]);
   });
 
+  it('run step - navigation failure', async () => {
+    const url = 'http://blah/';
+    const j1 = journey('j1', async ({ page }) => {
+      step('cannot resoslve url', async () => {
+        await page.goto(url);
+      });
+    });
+    const context = await Runner.createContext({});
+    await runner.registerJourney(j1, context);
+    const result = await runner.runSteps(j1, context, {});
+    await Gatherer.dispose(context.driver);
+    expect(result).toEqual([
+      {
+        status: 'failed',
+        url,
+        error: expect.any(Error),
+      },
+    ]);
+  });
+
+  it('run step - bad navigation', async () => {
+    const j1 = journey('j1', async ({ page }) => {
+      step('bad url', async () => {
+        await page.goto('blah');
+      });
+    });
+    const context = await Runner.createContext({});
+    await runner.registerJourney(j1, context);
+    const result = await runner.runSteps(j1, context, {});
+    await Gatherer.dispose(context.driver);
+    expect(result).toEqual([
+      {
+        status: 'failed',
+        url: 'about:blank',
+        error: expect.any(Error),
+      },
+    ]);
+  });
+
   it('run steps - accumulate results', async () => {
     const error = new Error('broken step 2');
     const j1 = journey('j1', async ({ page }) => {
diff --git a/__tests__/e2e/docker-compose.yml b/__tests__/e2e/docker-compose.yml
index 4f738e6..2720ed3 100644
--- a/__tests__/e2e/docker-compose.yml
+++ b/__tests__/e2e/docker-compose.yml
@@ -44,13 +44,13 @@ services:
     networks:
       - elastic
   synthetic:
-    image: docker.elastic.co/experimental/synthetics:${SYNTHETICS_VERSION}-synthetics
+    image: docker.elastic.co/beats/heartbeat:${STACK_VERSION}
     container_name: synthetics
     depends_on:
       - elasticsearch
     volumes:
       - ../../examples/docker/heartbeat.docker.yml:/usr/share/heartbeat/heartbeat.yml:ro
-      - ../../:/opt/elastic-synthetics:rw
+      - ../../:/opt/elastic-synthetics:ro
     security_opt:
       - seccomp=../../examples/docker/seccomp_profile.json
     environment:
diff --git a/__tests__/e2e/scripts/setup.sh b/__tests__/e2e/scripts/setup.sh
index b5ca174..e2fcda7 100755
--- a/__tests__/e2e/scripts/setup.sh
+++ b/__tests__/e2e/scripts/setup.sh
@@ -33,4 +33,4 @@ echo "" # newline
 echo "${bold}Starting elasticsearch , kibana and synthetics docker${normal}"
 echo "" # newline
 
-STACK_VERSION=8.0.0-SNAPSHOT SYNTHETICS_VERSION=8.0.0 docker-compose --file docker-compose.yml up --remove-orphans > ${TMP_DIR}/docker-logs.log 2>&1 &
+STACK_VERSION=8.0.0-SNAPSHOT docker-compose --file docker-compose.yml up --remove-orphans > ${TMP_DIR}/docker-logs.log 2>&1 &
diff --git a/__tests__/e2e/uptime.journey.ts b/__tests__/e2e/uptime.journey.ts
index 27acda4..505ccc3 100644
--- a/__tests__/e2e/uptime.journey.ts
+++ b/__tests__/e2e/uptime.journey.ts
@@ -55,7 +55,7 @@ journey('E2e test synthetics', async ({ page }) => {
   });
 
   step('Click on my monitor', async () => {
-    await page.click('[data-test-subj=monitor-page-link-my-monitor]');
+    await page.click('[data-test-subj=monitor-page-link-my-monitor-inline]');
   });
 
   step('It navigates to details page', async () => {
@@ -77,7 +77,7 @@ async function waitForSyntheticsData() {
               filter: [
                 {
                   term: {
-                    'monitor.id': 'my-monitor',
+                    'monitor.id': 'my-monitor-inline',
                   },
                 },
                 {
diff --git a/__tests__/reporters/__snapshots__/json.test.ts.snap b/__tests__/reporters/__snapshots__/json.test.ts.snap
index 68c4b5d..a36d5dc 100644
--- a/__tests__/reporters/__snapshots__/json.test.ts.snap
+++ b/__tests__/reporters/__snapshots__/json.test.ts.snap
@@ -279,7 +279,7 @@ Object {
 exports[`json reporter writes each step as NDJSON to the FD 1`] = `
 "{\\"type\\":\\"journey/register\\",\\"@timestamp\\":1600300800000000,\\"journey\\":{\\"name\\":\\"j1\\",\\"id\\":\\"j1\\"},\\"root_fields\\":{\\"os\\":{\\"platform\\":\\"darwin\\"},\\"package\\":{\\"name\\":\\"@elastic/synthetics\\",\\"version\\":\\"0.0.1\\"}},\\"package_version\\":\\"0.0.1\\"}
 {\\"type\\":\\"journey/start\\",\\"@timestamp\\":1600300800000000,\\"journey\\":{\\"name\\":\\"j1\\",\\"id\\":\\"j1\\"},\\"root_fields\\":{\\"os\\":{\\"platform\\":\\"darwin\\"},\\"package\\":{\\"name\\":\\"@elastic/synthetics\\",\\"version\\":\\"0.0.1\\"}},\\"payload\\":{\\"params\\":{},\\"source\\":\\"async () => { }\\"},\\"package_version\\":\\"0.0.1\\"}
-{\\"type\\":\\"step/screenshot\\",\\"@timestamp\\":1600300800000000,\\"journey\\":{\\"name\\":\\"j1\\",\\"id\\":\\"j1\\"},\\"step\\":{\\"name\\":\\"s1\\",\\"index\\":1},\\"root_fields\\":{\\"os\\":{\\"platform\\":\\"darwin\\"},\\"package\\":{\\"name\\":\\"@elastic/synthetics\\",\\"version\\":\\"0.0.1\\"}},\\"blob\\":\\"dummy\\",\\"package_version\\":\\"0.0.1\\"}
+{\\"type\\":\\"step/screenshot\\",\\"@timestamp\\":1600300800000000,\\"journey\\":{\\"name\\":\\"j1\\",\\"id\\":\\"j1\\"},\\"step\\":{\\"name\\":\\"s1\\",\\"index\\":1},\\"root_fields\\":{\\"os\\":{\\"platform\\":\\"darwin\\"},\\"package\\":{\\"name\\":\\"@elastic/synthetics\\",\\"version\\":\\"0.0.1\\"}},\\"blob\\":\\"dummy\\",\\"blob_mime\\":\\"image/jpeg\\",\\"package_version\\":\\"0.0.1\\"}
 {\\"type\\":\\"step/end\\",\\"@timestamp\\":1600300800000000,\\"journey\\":{\\"name\\":\\"j1\\",\\"id\\":\\"j1\\"},\\"step\\":{\\"name\\":\\"s1\\",\\"index\\":1,\\"status\\":\\"succeeded\\"},\\"root_fields\\":{\\"os\\":{\\"platform\\":\\"darwin\\"},\\"package\\":{\\"name\\":\\"@elastic/synthetics\\",\\"version\\":\\"0.0.1\\"}},\\"payload\\":{\\"source\\":\\"async () => { }\\",\\"start\\":0,\\"end\\":10,\\"url\\":\\"dummy\\",\\"status\\":\\"succeeded\\"},\\"url\\":\\"dummy\\",\\"package_version\\":\\"0.0.1\\"}
 {\\"type\\":\\"journey/network_info\\",\\"@timestamp\\":1600300800000000,\\"journey\\":{\\"name\\":\\"j1\\",\\"id\\":\\"j1\\"},\\"root_fields\\":{\\"user_agent\\":{},\\"http\\":{\\"request\\":{\\"body\\":{\\"bytes\\":0,\\"content\\":\\"\\"}}},\\"os\\":{\\"platform\\":\\"darwin\\"},\\"package\\":{\\"name\\":\\"@elastic/synthetics\\",\\"version\\":\\"0.0.1\\"}},\\"payload\\":{\\"browser\\":{},\\"is_navigation_request\\":true},\\"package_version\\":\\"0.0.1\\"}
 {\\"type\\":\\"journey/filmstrips\\",\\"@timestamp\\":1600300800000000,\\"journey\\":{\\"name\\":\\"j1\\",\\"id\\":\\"j1\\"},\\"root_fields\\":{\\"os\\":{\\"platform\\":\\"darwin\\"},\\"package\\":{\\"name\\":\\"@elastic/synthetics\\",\\"version\\":\\"0.0.1\\"}},\\"payload\\":{\\"index\\":0,\\"startTime\\":392583.998697,\\"ts\\":392583998697},\\"blob\\":\\"dummy\\",\\"package_version\\":\\"0.0.1\\"}
diff --git a/__tests__/reporters/json.test.ts b/__tests__/reporters/json.test.ts
index 6ab59d8..de520bb 100644
--- a/__tests__/reporters/json.test.ts
+++ b/__tests__/reporters/json.test.ts
@@ -194,4 +194,23 @@ describe('json reporter', () => {
 
     expect((await readAndCloseStream()).toString()).toMatchSnapshot();
   });
+
+  it('captures screenshots blob and mime type', async () => {
+    const data = 'aaaaaaaaaaa';
+    runner.emit('step:end', {
+      journey: j1,
+      status: 'failed',
+      step: step('s2', () => {}),
+      screenshot: data,
+      start: 11,
+      end: 20,
+    });
+    const stepEnd = (await readAndCloseStreamJson()).find(
+      json => json.type == 'step/screenshot'
+    );
+    expect(stepEnd).toMatchObject({
+      blob: data,
+      blob_mime: 'image/jpeg',
+    });
+  });
 });
diff --git a/examples/docker/pull-latest.sh b/examples/docker/pull-latest.sh
index d8654d0..caeab30 100755
--- a/examples/docker/pull-latest.sh
+++ b/examples/docker/pull-latest.sh
@@ -1,5 +1,2 @@
 #!/usr/bin/env bash
-docker pull docker.elastic.co/experimental/synthetics:7.10.0-synthetics
-docker pull docker.elastic.co/experimental/synthetics:8.0.0-synthetics
-docker pull docker.elastic.co/observability-ci/heartbeat:7.10.0-synthetics
-docker pull docker.elastic.co/observability-ci/heartbeat:8.0.0-synthetics
+docker pull docker.elastic.co/beats/heartbeat:8.0.0-SNAPSHOT