├── .buildkite ├── hooks │ └── pre-command ├── release.yml └── scripts │ ├── create-github-release.sh │ ├── download-signed-artifacts.sh │ └── package.sh ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── feature.yml │ └── other.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── catalog-info.yml │ ├── test-docs.yml │ └── test.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE.txt ├── README.md ├── RELEASE.md ├── assets └── entitlements.mac.plist ├── catalog-info.yaml ├── common ├── helper │ └── test │ │ └── createAction.ts └── types.d.ts ├── demo-app ├── .gitignore ├── .npmrc ├── README.md ├── components │ ├── Ad.js │ ├── CheckoutDetails.js │ ├── Footer.js │ ├── Header.js │ ├── Price.js │ ├── Product.js │ └── Recommendations.js ├── package.json ├── pages │ ├── api │ │ ├── cart │ │ │ ├── checkout.js │ │ │ └── index.js │ │ ├── index.js │ │ └── product │ │ │ ├── [productId].js │ │ │ └── index.js │ ├── cart │ │ ├── checkout.js │ │ └── index.js │ ├── index.js │ └── product │ │ └── [productId].js ├── public │ ├── favicon.ico │ └── static │ │ └── img │ │ └── products │ │ ├── air-plant.jpg │ │ ├── barista-kit.jpg │ │ ├── camera-lens.jpg │ │ ├── camp-mug.jpg │ │ ├── city-bike.jpg │ │ ├── credits.txt │ │ ├── film-camera.jpg │ │ ├── record-player.jpg │ │ ├── terrarium.jpg │ │ └── typewriter.jpg └── utils │ └── storage.js ├── dev-tools ├── dep-info.js └── find-unused-exports.js ├── docs ├── DOWNLOAD.md ├── e2e_1.png ├── e2e_2.png ├── e2e_3.png └── e2e_tests.md ├── e2e ├── .dockerignore ├── Dockerfile.jenkins ├── services │ ├── browser.ts │ ├── electron.ts │ ├── env.ts │ └── index.ts ├── setup │ ├── builder.js │ ├── demo_app.js │ └── index.js ├── teardown │ ├── demo_app.js │ └── index.js └── tests │ ├── actionValues.test.ts │ ├── assertion.test.ts │ ├── export.test.ts │ ├── navigation.test.ts │ ├── pauseAndResume.test.ts │ ├── runTest.test.ts │ ├── stepDividers.test.ts │ └── testServer.ts ├── electron-builder.linux-x64.yml ├── electron-builder.mac-arm64.yml ├── electron-builder.mac-x64.yml ├── electron-builder.win-x64.yml ├── electron ├── api │ ├── exportScript.ts │ ├── generateCode.ts │ ├── index.ts │ ├── openExternalLink.ts │ ├── recordJourney.ts │ ├── runJourney.ts │ └── setMode.ts ├── browserManager.ts ├── config.ts ├── electron.ts ├── execution.ts ├── menu.ts ├── preload.ts └── syntheticsManager.ts ├── jest.e2e.config.js ├── jest.unit.config.js ├── nodemon.json ├── package-lock.json ├── package.json ├── public ├── elastic.png ├── favicon.png └── index.html ├── scripts ├── after-pack.js ├── before-pack.js ├── download-chromium.js ├── fix-sharp.js ├── install-pw.js └── notarize.js ├── src ├── App.tsx ├── common │ ├── shared.test.ts │ ├── shared.ts │ └── types.ts ├── components │ ├── ActionDetail │ │ ├── ActionDetail.test.tsx │ │ ├── ActionDetail.tsx │ │ ├── FormControl.test.tsx │ │ ├── FormControl.tsx │ │ └── index.ts │ ├── ActionElement │ │ ├── ActionElement.test.tsx │ │ ├── ActionElement.tsx │ │ ├── AssertionHeadingText.tsx │ │ ├── Behavior.tsx │ │ ├── ControlButton.tsx │ │ ├── ExtraActions.tsx │ │ ├── HeadingText.tsx │ │ ├── NewStepDividerButton.tsx │ │ ├── SettingsPopover.tsx │ │ ├── index.ts │ │ └── styles.ts │ ├── ActionStatusIndicator.tsx │ ├── AppPageBody.tsx │ ├── Assertion │ │ ├── Assertion.tsx │ │ ├── AssertionInfo.tsx │ │ ├── Select.tsx │ │ └── index.ts │ ├── ControlButton.tsx │ ├── ExportScriptFlyout │ │ ├── Body.test.tsx │ │ ├── Body.tsx │ │ ├── Flyout.test.tsx │ │ ├── Flyout.tsx │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ └── index.ts │ ├── Header │ │ ├── HeaderControls.test.tsx │ │ ├── HeaderControls.tsx │ │ ├── StatusIndicator.tsx │ │ ├── Title.tsx │ │ ├── UrlField.test.tsx │ │ └── UrlField.tsx │ ├── SaveCodeButton.test.tsx │ ├── SaveCodeButton.tsx │ ├── StartOverWarningModal.tsx │ ├── StepSeparator │ │ ├── EditStepNameInput.test.tsx │ │ ├── EditStepNameInput.tsx │ │ ├── SeparatorActions.test.tsx │ │ ├── SeparatorActions.tsx │ │ ├── index.tsx │ │ └── styles.ts │ ├── TestButton.tsx │ └── TestResult │ │ ├── ResultBody.tsx │ │ ├── ResultErrorBody.tsx │ │ ├── ResultFlyoutItem.test.tsx │ │ ├── ResultFlyoutItem.tsx │ │ ├── ResultTitle.test.tsx │ │ ├── ResultTitle.tsx │ │ ├── TestResult.test.tsx │ │ ├── TestResult.tsx │ │ ├── TestResultFlyoutHeader.test.tsx │ │ ├── TestResultFlyoutHeader.tsx │ │ ├── TruncatedTitle.test.tsx │ │ ├── TruncatedTitle.tsx │ │ ├── index.ts │ │ └── styles.tsx ├── contexts │ ├── CommunicationContext.ts │ ├── DragAndDropContext.ts │ ├── RecordingContext.ts │ ├── StepsContext.ts │ ├── StyledComponentsEuiProvider.tsx │ ├── TestContext.ts │ ├── ToastContext.ts │ └── UrlContext.ts ├── global.d.ts ├── helpers │ ├── generator.test.ts │ ├── generator.ts │ ├── resultReducer.test.ts │ ├── resultReducer.ts │ └── test │ │ ├── RenderContexts.tsx │ │ ├── defaults.ts │ │ ├── index.ts │ │ ├── mockApi.ts │ │ └── render.tsx ├── hooks │ ├── useDragAndDrop.test.ts │ ├── useDragAndDrop.ts │ ├── useDragAndDropContext.ts │ ├── useDrop.test.tsx │ ├── useDrop.ts │ ├── useGlobalToasts.test.ts │ ├── useGlobalToasts.ts │ ├── useRecordingContext.test.ts │ ├── useRecordingContext.ts │ ├── useStepsContext │ │ ├── index.ts │ │ ├── onDropStep.ts │ │ ├── useStepsContext.test.ts │ │ └── useStepsContext.ts │ ├── useSyntheticsTest.test.tsx │ ├── useSyntheticsTest.ts │ ├── useTestResult.test.tsx │ └── useTestResult.ts ├── index.tsx ├── react-app-env.d.ts └── renderer.d.ts ├── tests └── common │ └── resolver.js ├── tsconfig.base.json ├── tsconfig.json └── tsconfig.node.json /.buildkite/hooks/pre-command: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ## This script prepares the secret context 3 | ## 4 | ## NOTE: *_SECRET or *_TOKEN env variables are masked, hence if you'd like to avoid any 5 | ## surprises please use the suffix _SECRET or _TOKEN for those values that contain 6 | ## any sensitive data. Buildkite can mask those values automatically 7 | 8 | set -eo pipefail 9 | 10 | # To help with testing the GPG signing 11 | BUILDKITE_TOKEN_SECRET=$(vault kv get -field=buildkite_token kv/ci-shared/observability-ci/buildkite-read-build-access) 12 | export BUILDKITE_TOKEN_SECRET 13 | 14 | # If not Git tag release then DRY_RUN=true 15 | DRY_RUN=true 16 | if [ -n "${BUILDKITE_TAG}" ] ; then 17 | DRY_RUN=false 18 | fi 19 | export DRY_RUN 20 | 21 | # Upload should only allow configuring the Buildkite Token ih the pre-command. 22 | if [[ "$BUILDKITE_COMMAND" =~ .*"upload".* ]]; then 23 | echo "Skipped pre-command when running the Upload pipeline" 24 | # NOTE: exit 0 with the pre-command does not work! 25 | else 26 | echo "~~~ Configure ephemeral GitHub token" 27 | GITHUB_TOKEN=$VAULT_GITHUB_TOKEN 28 | GH_TOKEN=$VAULT_GITHUB_TOKEN 29 | export GH_TOKEN GITHUB_TOKEN 30 | set +x 31 | set +e 32 | echo "~~~ Install nvm" 33 | touch ~/.zshrc # See https://github.com/nvm-sh/nvm?tab=readme-ov-file#troubleshooting-on-macos 34 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash 35 | fi 36 | -------------------------------------------------------------------------------- /.buildkite/scripts/create-github-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Download the signed artifacts and run the GitHub release using the GH cli 3 | # 4 | # Required environment variables: 5 | # - BUILDKITE_TAG 6 | # 7 | set -eox pipefail 8 | 9 | DIST_LOCATION=signed-artifacts 10 | echo "--- Download signed artifacts" 11 | # 12 | # Download the signed artifacts from the previous step but using the below order 13 | # gpg, windows and macos. 14 | # This should help with the signing process and download the files in the correct 15 | # order. gpg signing signs all the files, but the dmg files need to be signed 16 | # separately as part of the Macos BK pipeline helper. 17 | # As long as, the signing process is split in different types and use the same 18 | # folder name, we need this hack. 19 | # 20 | buildkite-agent artifact download --step gpg "$DIST_LOCATION/*.*" ./ 21 | # help with debugging 22 | ls -ltra "$DIST_LOCATION/" 23 | buildkite-agent artifact download --step windows "$DIST_LOCATION/*.*" ./ 24 | ls -ltra "$DIST_LOCATION/" 25 | buildkite-agent artifact download --step macos "$DIST_LOCATION/*.*" ./ 26 | ls -ltra "$DIST_LOCATION/" 27 | 28 | echo "--- List signed artifacts" 29 | ls -l "$DIST_LOCATION/" 30 | 31 | echo "--- Install gh :github:" 32 | if ! gh --version &>/dev/null ; then 33 | wget -q https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_linux_amd64.tar.gz -O gh.tar.gz 34 | tar -xpf gh.tar.gz --strip-components=2 35 | PATH="$(pwd):${PATH}" 36 | export PATH 37 | gh --version 38 | fi 39 | 40 | echo "--- Run GitHub release" 41 | if [ -n "${BUILDKITE_TAG}" ] ; then 42 | 43 | if [ ! -d "$DIST_LOCATION" ] ; then 44 | echo "No signed artifacts found in ${DIST_LOCATION}" 45 | exit 1 46 | fi 47 | 48 | # VAULT_GITHUB_TOKEN is the GitHub ephemeral token created in Buildkite 49 | GH_TOKEN=$VAULT_GITHUB_TOKEN \ 50 | gh release \ 51 | create \ 52 | "${BUILDKITE_TAG}" \ 53 | --draft \ 54 | --generate-notes \ 55 | --repo "elastic/synthetics-recorder" \ 56 | "${DIST_LOCATION}"/*.* 57 | else 58 | echo "gh release won't be triggered this is not a Git tag release, but let's list the releases" 59 | # VAULT_GITHUB_TOKEN is the GitHub ephemeral token created in Buildkite 60 | GH_TOKEN=$VAULT_GITHUB_TOKEN \ 61 | gh release list --repo elastic/synthetics-recorder 62 | fi 63 | -------------------------------------------------------------------------------- /.buildkite/scripts/download-signed-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Create a dynamic buildkite step with the artifacts to be downloaded 4 | # 5 | # Required environment variables: 6 | # - BUILDKITE_TOKEN_SECRET 7 | # 8 | 9 | STEP=$1 10 | DOWNLOAD_STEP_NAME=$2 11 | 12 | ## Support main pipeline and downstream pipelines 13 | if [ -n "$BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG" ] ; then 14 | BUILDKITE_PIPELINE_SLUG=$BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG 15 | BUILDKITE_BUILD_NUMBER=$BUILDKITE_TRIGGERED_FROM_BUILD_NUMBER 16 | fi 17 | 18 | ## Fail if no token 19 | if [ -z "$BUILDKITE_TOKEN_SECRET" ] ; then 20 | echo "Token could not be loaded from vault. Please review .buildkite/hooks/pre-command" 21 | exit 1 22 | fi 23 | 24 | BUILDS_URL="https://api.buildkite.com/v2/organizations/elastic/pipelines/$BUILDKITE_PIPELINE_SLUG/builds" 25 | build_json=$(curl -sH "Authorization: Bearer $BUILDKITE_TOKEN_SECRET" "$BUILDS_URL/$BUILDKITE_BUILD_NUMBER") 26 | # sign-service is the pipeline step in .buildkite/release.yml 27 | SIGN_BUILD_ID=$(jq -r ".jobs[] | select(.step_key == \"$STEP\").triggered_build.id" <<< "$build_json") 28 | 29 | ## Fail if no build id 30 | if [ -z "$SIGN_BUILD_ID" ] ; then 31 | echo "Sign build id could not be found. Please review $BUILDS_URL/$BUILDKITE_BUILD_NUMBER and the below json output:" 32 | echo "$build_json" 33 | curl -sH "Authorization: Bearer $BUILDKITE_TOKEN_SECRET" "$BUILDS_URL" 34 | exit 1 35 | fi 36 | 37 | cat << EOF 38 | - label: ":pipeline: Download signed artifacts $DOWNLOAD_STEP_NAME" 39 | key: "$DOWNLOAD_STEP_NAME" 40 | commands: 41 | - mkdir -p signed-artifacts 42 | - buildkite-agent artifact download --build "$SIGN_BUILD_ID" "*.*" signed-artifacts/ 43 | - ls -ltra signed-artifacts/ 44 | - buildkite-agent artifact upload "signed-artifacts/*.*" 45 | agents: 46 | image: docker.elastic.co/ci-agent-images/ubuntu-build-essential 47 | EOF -------------------------------------------------------------------------------- /.buildkite/scripts/package.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exo pipefail 3 | 4 | echo "~~~ Load nvm" 5 | if [ -n "$BUILDKITE" ] ; then 6 | set +xe 7 | # Need to figure out what's the reason NVM is not explictly loaded 8 | source "$HOME/.zshrc" 9 | fi 10 | 11 | echo "--- Install node and gather dependencies" 12 | nvm install "$(cat .nvmrc)" 13 | npm ci 14 | 15 | echo "--- run release-ci" 16 | # Disable signing 17 | # see https://www.electron.build/code-signing#how-to-disable-code-signing-during-the-build-process-on-macos 18 | export CSC_IDENTITY_AUTO_DISCOVERY=false 19 | # Disable notarize, see scripts/notarize.js 20 | export SKIP_NOTARIZATION=true 21 | 22 | if [ -z "$PACKAGE_PLATFORM" ]; then 23 | echo "Error: PACKAGE_PLATFORM must be set to continue." 24 | exit 1 25 | fi 26 | 27 | case "$PACKAGE_PLATFORM" in 28 | "linux") 29 | echo "--- Package for Linux" 30 | npm run release-ci_linux-x64 | tee package.log 31 | ;; 32 | 33 | "macos_arm64") 34 | echo "--- Package for MacOS ARM64" 35 | npm run release-ci_mac-arm64 | tee package.log 36 | ;; 37 | 38 | "macos_x64") 39 | echo "--- Package for MacOS x64" 40 | npm run release-ci_mac-x64 | tee package.log 41 | ;; 42 | 43 | "windows") 44 | echo "--- Package for Windows" 45 | npm run release-ci_windows-x64 | tee package.log 46 | ;; 47 | esac 48 | 49 | # Store unsigned artifacts 50 | if [ -n "$BUILDKITE" ] ; then 51 | echo "--- Upload artifacts" 52 | mv dist artifacts-to-sign 53 | # We cannot upload artifacts-to-sign/*.* since it contains some generated files builder-debug, .blockmap and so on 54 | # (only *nix) 55 | buildkite-agent artifact upload "artifacts-to-sign/*.deb;artifacts-to-sign/*.dmg;artifacts-to-sign/*.zip" 56 | 57 | # (only windows) 58 | buildkite-agent artifact upload "artifacts-to-sign/*.exe" 59 | 60 | # (only mac) 61 | buildkite-agent artifact upload "artifacts-to-sign/*.dmg" 62 | fi 63 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{md,asciidoc}] 13 | trim_trailing_whitespace = false 14 | insert_final_newline = false 15 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PLAYWRIGHT_SKIP_BROWSER_GC=1 2 | PLAYWRIGHT_BROWSERS_PATH=local-browsers 3 | SKIP_PREFLIGHT_CHECK=true 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore linting the demo app 2 | demo-app 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-present, Elastic NV 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | const path = require('path'); 26 | const { readFileSync } = require('fs'); 27 | const LICENSE = readFileSync(path.join(__dirname, 'LICENSE'), 'utf-8'); 28 | const LICENSE_HEADER = '\n' + LICENSE; 29 | 30 | module.exports = { 31 | env: { 32 | es2021: true, 33 | }, 34 | plugins: ['header', 'prettier'], 35 | extends: ['react-app', 'plugin:react/recommended', 'prettier'], 36 | ignorePatterns: ['build', 'local-browsers', 'demo-app'], 37 | overrides: [ 38 | { 39 | extends: ['eslint:recommended'], 40 | files: ['*.js', '*.jsx'], 41 | }, 42 | { 43 | extends: [ 44 | 'plugin:@typescript-eslint/recommended', 45 | 'plugin:@typescript-eslint/eslint-recommended', 46 | ], 47 | files: ['*.ts', '*.tsx'], 48 | plugins: ['@typescript-eslint'], 49 | rules: { 50 | '@typescript-eslint/explicit-module-boundary-types': 0, 51 | '@typescript-eslint/no-non-null-assertion': 0, 52 | '@typescript-eslint/no-explicit-any': 0, 53 | }, 54 | }, 55 | ], 56 | rules: { 57 | 'default-case': 0, 58 | 'header/header': [2, 'block', LICENSE_HEADER], 59 | 'no-console': 1, 60 | 'prettier/prettier': 2, 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 🐛 2 | description: Something is not behaving as expected. 3 | title: '[Bug] ' 4 | labels: ['bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | # Before creating your issue 10 | - type: markdown 11 | attributes: 12 | value: | 13 | ## Troubleshoot locally 14 | Please try to ensure that the bug you're reporting is _not_ caused by any misconfiguration on your machine or network. Ideally, try to reproduce the problem on a different environment. 15 | - type: markdown 16 | attributes: 17 | value: | 18 | ## Identify the steps to reproduce the issue 19 | The fewer and simpler the steps we can take to reproduce your issue, the sooner we can get back to you, and solve your problem. 20 | If possible, please include a GIF or screenshots for each of the steps you followed to reproduce the bug. 21 | Please provide as much relevant information as possible about your environment if you think that could be interfering with the app's behavior. 22 | - type: textarea 23 | id: bug_summary 24 | attributes: 25 | label: Bug summary 26 | description: A concise description of the bug. 27 | placeholder: The recorder crashed when I started recording. 28 | validations: 29 | required: true 30 | - type: input 31 | id: version 32 | attributes: 33 | label: Recorder Version 34 | description: | 35 | Which version of the script recorder are you using? 36 | You can see the recorder's version number in the "Elastic Synthetics Recorder > About" menu. 37 | If you're unsure, provide us with the date in which you've downloaded it (if you remember it). 38 | placeholder: 1.0.1 39 | validations: 40 | required: false 41 | - type: textarea 42 | id: how_to_reproduce 43 | attributes: 44 | label: Steps to reproduce 45 | description: Describe the steps to reproduce your issue. 46 | placeholder: | 47 | Example issue: 48 | 1. Start the script recorder 49 | 2. Type www.example.com in the main URL field 50 | 3. Click start recording 51 | 4. Do XYZ and you'll see that undesired behavior ABC happens. 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: expected 56 | attributes: 57 | label: Expected behavior 58 | description: Explain what should the desired behaviour be. 59 | placeholder: XYZ should not appear 60 | validations: 61 | required: false 62 | - type: textarea 63 | id: context 64 | attributes: 65 | label: Additional information 66 | description: Any other relevant pieces of information. 67 | validations: 68 | required: false 69 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Enhancement 🚀 2 | description: Propose a new feature or enhancement. 3 | title: '[Enhancement] ' 4 | labels: ['enhancement'] 5 | body: 6 | - type: textarea 7 | id: feature_summary 8 | attributes: 9 | label: Feature summary 10 | description: A description of the feature. 11 | placeholder: I want the script recorder to be able to assert on the page's title. 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: feature_reason 16 | attributes: 17 | label: Why is this feature important? 18 | description: | 19 | Explain why this feature is important for our users, or how it makes the script recorder better. 20 | placeholder: Allowing the recorder to assert on the page's title is a use-case that 99% of our users have requested, as it's a crucial part of a web page. 21 | validations: 22 | required: false 23 | - type: textarea 24 | id: resources 25 | attributes: 26 | label: Linked resources 27 | description: Link any designs or other resources which might be relevant for implementing this feature. 28 | validations: 29 | required: false 30 | - type: textarea 31 | id: context 32 | attributes: 33 | label: Additional information 34 | description: Any other relevant pieces of information. 35 | validations: 36 | required: false 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: Other ⚙ 2 | description: Register a chore, or some other type of task. 3 | title: '[Other] ' 4 | labels: ['chore'] 5 | body: 6 | - type: textarea 7 | id: task_summary 8 | attributes: 9 | label: Task summary 10 | description: A description of the task. 11 | placeholder: We must implement the infrastructure for E2E tests. 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: resources 16 | attributes: 17 | label: Linked resources 18 | description: Link any designs or other resources which might be relevant for implementing this feature. 19 | validations: 20 | required: false 21 | - type: textarea 22 | id: context 23 | attributes: 24 | label: Additional information 25 | description: Any other relevant pieces of information. 26 | validations: 27 | required: false 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## Summary 6 | 7 | 8 | 9 | 10 | 11 | ## Implementation details 12 | 13 | 14 | 15 | 16 | 17 | ## How to validate this change 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | allow: 13 | - dependency-type: "production" 14 | ignore: 15 | - dependency-name: "*" 16 | update-types: ["version-update:semver-patch"] 17 | 18 | # GitHub actions 19 | - package-ecosystem: "github-actions" 20 | directory: "/" 21 | reviewers: 22 | - "elastic/observablt-ci" 23 | schedule: 24 | interval: "weekly" 25 | day: "sunday" 26 | time: "22:00" 27 | groups: 28 | github-actions: 29 | patterns: 30 | - "*" 31 | -------------------------------------------------------------------------------- /.github/workflows/catalog-info.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: catalog-info 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - main 8 | paths: 9 | - 'catalog-info.yaml' 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | validate: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: read 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: elastic/oblt-actions/elastic/validate-catalog@v1 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/test-docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This workflow sets the Test / test status check to success in case it's a docs only PR and test.yml is not triggered 3 | # https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks 4 | name: Test # The name must be the same as in test.yml 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | paths-ignore: # This expression needs to match the paths ignored on test.yml. 11 | - '**' 12 | - '!**/*.md' 13 | pull_request: 14 | paths-ignore: # This expression needs to match the paths ignored on test.yml. 15 | - '**' 16 | - '!**/*.md' 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | test: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - run: 'echo "No build required"' -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '**.md' 8 | pull_request: 9 | paths-ignore: 10 | - '**.md' 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test: 17 | runs-on: macos-13 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version-file: '.nvmrc' 23 | cache: 'npm' 24 | - name: Install modules 25 | run: npm ci 26 | - name: Lint 27 | run: npm run lint 28 | - name: Unused exports 29 | run: npm run unused-exports 30 | - name: Build 31 | run: npm run build 32 | - name: Run test 33 | run: npm test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | build 4 | node_modules 5 | journeys/*.js 6 | 7 | local-browsers 8 | .synthetics 9 | react-app-env.d.ts 10 | 11 | eslint-junit.xml 12 | .idea 13 | *-junit.xml 14 | .DS_Store 15 | tsconfig.tsbuildinfo 16 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | npm run unused-exports 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | engine-strict=true 3 | PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=true 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.10.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | local-browsers 4 | demo-app 5 | coverage 6 | *.yml 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions to this repository will be very welcome, both in the form of issues or pull-requests 😊 4 | 5 | Before starting to work on a particular piece of code, please first open an issue to discuss the change before starting to work on it. 6 | 7 | Please note that Elastic does have a [Code of Conduct](https://www.elastic.co/community/codeofconduct). 8 | 9 | # Creating an issue 10 | 11 | We expect contributors to fill our issue templates when creating new issues. It's important that contributors use those templates appropriately because doing so helps us triage issues more quickly, discuss them more efficiently, and respond to the issue's participants sooner. 12 | 13 | For bugs, for example, we expect contributors to provide clear instructions on how to reproduce the bug they've found, and, if possible a screenshot or GIF illustrating the problem. We also appreciate it when contributors can provide information about the bug's cause, even though that's not necessary for reporting a bug. 14 | 15 | Once contributors create an issue, this project's maintainers will review and triage it, and we'll aim to get back to the issue's creator as soon as we can, although we can't promise any particular dates. 16 | 17 | # Opening a pull-request 18 | 19 | Before submitting your PR, ensure it complies with the specs in the issue you're solving, and run both the tests and linting tasks specified in `package.json`. These are required by CI, so they need to be passing before you can merge your pull-request. 20 | 21 | When opening a pull-request, make sure to specify which issue it solves, so that the PR and issue get linked. Do the same in your commit messages by adding the issue number to it. 22 | 23 | Just like when opening an issue, it's important to fill all the sections in our pull-request templates appropriately, so that we can review your contribution sooner, and provide actionable feedback in less time. 24 | 25 | After your issue is opened **and CI is passing**, the team will do its best to review it and get back to you as soon as we can, although we can't commit to a specific timeframe. 26 | 27 | Please note that you'll have to sign [Elastic's CLA to be able to contribute code to this repo](https://www.elastic.co/contributor-agreement). 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present, Elastic NV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-dyld-environment-variables 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | com.apple.security.cs.allow-unsigned-executable-memory 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | # Declare your Buildkite pipelines below 2 | --- 3 | # yaml-language-server: $schema=https://gist.githubusercontent.com/elasticmachine/988b80dae436cafea07d9a4a460a011d/raw/rre.schema.json 4 | apiVersion: backstage.io/v1alpha1 5 | kind: Resource 6 | metadata: 7 | name: buildkite-pipeline-synthetics-recorder 8 | description: Buildkite Pipeline for Releasing the synthetics-recorder 9 | links: 10 | - title: Pipeline 11 | url: https://buildkite.com/elastic/synthetics-recorder-release 12 | spec: 13 | type: buildkite-pipeline 14 | owner: group:observablt-robots 15 | system: buildkite 16 | implementation: 17 | apiVersion: buildkite.elastic.dev/v1 18 | kind: Pipeline 19 | metadata: 20 | name: synthetics-recorder-release 21 | description: Buildkite Pipeline for Releasing the synthetics-recorder 22 | spec: 23 | repository: elastic/synthetics-recorder 24 | pipeline_file: ".buildkite/release.yml" 25 | allow_rebuilds: true 26 | cancel_intermediate_builds: false 27 | skip_intermediate_builds: false 28 | provider_settings: 29 | build_branches: false 30 | build_tags: true 31 | filter_condition: | # Eg v100.100.100 but skip v100.100.100.1 or v100.100.100-rc 32 | build.tag =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ 33 | filter_enabled: true 34 | teams: 35 | observablt-robots: 36 | access_level: MANAGE_BUILD_AND_READ 37 | uptime: 38 | access_level: MANAGE_BUILD_AND_READ 39 | everyone: 40 | access_level: READ_ONLY 41 | -------------------------------------------------------------------------------- /demo-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .env* 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | .vercel 28 | 29 | package-lock.json 30 | -------------------------------------------------------------------------------- /demo-app/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /demo-app/README.md: -------------------------------------------------------------------------------- 1 | # synthetics-ecommerce-demo 2 | Demo of @elastic/synthetics agent using a Ecommerce shop. 3 | -------------------------------------------------------------------------------- /demo-app/components/Ad.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Ad = ({ data }) => ( 4 |
5 |
6 | Advertisement: 7 | 13 | {data.text} 14 | 15 |
16 |
17 | ); 18 | 19 | export default Ad; 20 | -------------------------------------------------------------------------------- /demo-app/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Cookies from "js-cookie"; 3 | 4 | const Footer = () => ( 5 | <> 6 | 27 | 28 | ); 29 | 30 | export default Footer; 31 | -------------------------------------------------------------------------------- /demo-app/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Head from "next/head"; 3 | import Link from "next/link"; 4 | import Cookies from "js-cookie"; 5 | 6 | const Header = ({}) => { 7 | if (!Cookies.get("session_id") && global.window) { 8 | Cookies.set("session_id", performance.now() + "-" + Date.now()); 9 | } 10 | 11 | return ( 12 | <> 13 | 14 | 15 | 19 | 20 | Hipster Shop 21 | 27 | 28 |
29 |
30 |
31 | 32 | Hipster Shop 33 | 34 |
40 | 43 | 44 | 45 | View Cart 46 | 47 | 48 |
49 |
50 | 55 |
56 |
57 | 58 | ); 59 | }; 60 | 61 | export default Header; 62 | -------------------------------------------------------------------------------- /demo-app/components/Price.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const renderPrice = (data, key = "price_usd") => { 4 | const value = data[key]; 5 | const price = `${value.units}.${value.nanos || 0}`; 6 | return value.currency_code + " " + Number(price).toFixed(2); 7 | }; 8 | 9 | export const renderTotalCost = (items) => { 10 | const price = items.reduce((acc, { price_usd, quantity }) => { 11 | const value = `${price_usd.units}.${price_usd.nanos || 0}`; 12 | return acc + Number(value) * Number(quantity); 13 | }, 0); 14 | 15 | return price.toFixed(2); 16 | }; 17 | 18 | export const getShippingCost = () => (Math.random() * 4 + 3).toFixed(2); 19 | 20 | export const TotalPrice = ({ items }) => ( 21 |
22 |
23 | Total Cost: USD {renderTotalCost(items)} 24 |
25 |
26 | ); 27 | 28 | const Price = ({ data }) =>

{renderPrice(data)}

; 29 | export default Price; 30 | -------------------------------------------------------------------------------- /demo-app/components/Product.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Price from "../components/Price"; 3 | 4 | const Product = (prop) => { 5 | return ( 6 |
7 |
8 | 9 | {prop.description} 14 | 15 |
16 |
{prop.name}
17 |
18 |
19 | 20 | 26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default Product; 37 | -------------------------------------------------------------------------------- /demo-app/components/Recommendations.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Recommendation = ({ data }) => ( 4 |
5 |
6 | 7 | {data.description} 14 | 15 |
16 | {data.name} 17 |
18 |
19 |
20 | ); 21 | 22 | const Recommendations = ({ recommendations }) => ( 23 | <> 24 |
25 |
Products you might like
26 |
27 | {recommendations.map((recommendation, i) => ( 28 | 29 | ))} 30 |
31 | 32 | ); 33 | 34 | export default Recommendations; 35 | -------------------------------------------------------------------------------- /demo-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "synthetics-ecommerce-demo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "final-form": "^4.20.1", 12 | "js-cookie": "^2.2.1", 13 | "next": "12.1.0", 14 | "react": "17.0.1", 15 | "react-dom": "17.0.1", 16 | "react-final-form": "^6.5.2", 17 | "styled-jsx": "^5.0.0", 18 | "swr": "^0.3.9" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo-app/pages/api/cart/checkout.js: -------------------------------------------------------------------------------- 1 | import { getProduct, getRecommendedProducts } from "../"; 2 | 3 | const ORDER = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"; 4 | const TRACKING = "xx-xxxxxx-xxyxxx"; 5 | 6 | const createUniqueId = (str) => { 7 | return str.replace(/[xy]/g, (c) => { 8 | var r = (Math.random() * 16) | 0, 9 | v = c == "x" ? r : (r & 0x3) | 0x8; 10 | return v.toString(16); 11 | }); 12 | }; 13 | 14 | export default async function handler(req, res) { 15 | const itemsInSession = req.body; 16 | const recommendations = getRecommendedProducts(null).slice(0, 4); 17 | 18 | const order_id = createUniqueId(ORDER); 19 | const tracking_id = createUniqueId(TRACKING); 20 | 21 | if (!itemsInSession) { 22 | return res.json({ 23 | items: [], 24 | order_id, 25 | tracking_id, 26 | recommendations, 27 | }); 28 | } 29 | const items = itemsInSession.map((item) => ({ 30 | ...getProduct(item.id), 31 | quantity: item.quantity, 32 | })); 33 | 34 | res.json({ 35 | items, 36 | order_id, 37 | tracking_id, 38 | recommendations, 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /demo-app/pages/api/cart/index.js: -------------------------------------------------------------------------------- 1 | import { getProduct, getRecommendedProducts } from "../"; 2 | 3 | export default async function handler(req, res) { 4 | const itemsInSession = req.body; 5 | const recommendations = getRecommendedProducts(null).slice(0, 4); 6 | if (!itemsInSession) { 7 | return res.json({ 8 | items: [], 9 | recommendations, 10 | }); 11 | } 12 | const items = itemsInSession.map((item) => ({ 13 | ...getProduct(item.id), 14 | quantity: item.quantity, 15 | })); 16 | res.json({ items, recommendations }); 17 | } 18 | -------------------------------------------------------------------------------- /demo-app/pages/api/product/[productId].js: -------------------------------------------------------------------------------- 1 | import { getProduct, getRecommendedProducts } from "../"; 2 | 3 | export default async function handler(req, res) { 4 | const { productId } = req.query; 5 | const product = getProduct(productId); 6 | res.json({ product, recommendations: getRecommendedProducts().slice(0, 4) }); 7 | } 8 | -------------------------------------------------------------------------------- /demo-app/pages/api/product/index.js: -------------------------------------------------------------------------------- 1 | import { getProducts } from "../"; 2 | 3 | export default async function handler(req, res) { 4 | const products = getProducts(); 5 | res.json(products); 6 | } 7 | -------------------------------------------------------------------------------- /demo-app/pages/cart/checkout.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Header from "../../components/Header"; 3 | import Footer from "../../components/Footer"; 4 | import Recommendations from "../../components/Recommendations"; 5 | import { renderTotalCost, getShippingCost } from "../../components/Price"; 6 | import * as storage from "../../utils/storage"; 7 | 8 | const Checkout = ({}) => { 9 | const NoOrders = () => ( 10 | <> 11 |
12 |
13 |
14 |
15 | <> 16 |

No orders found

17 | 18 |
19 |
20 |
21 |