diff --git a/docs/docs/tools-and-integrations/img/tracetest-cloud-typescript.gif b/docs/docs/tools-and-integrations/img/tracetest-cloud-typescript.gif new file mode 100644 index 0000000000..3ff95e6775 Binary files /dev/null and b/docs/docs/tools-and-integrations/img/tracetest-cloud-typescript.gif differ diff --git a/docs/docs/tools-and-integrations/overview.mdx b/docs/docs/tools-and-integrations/overview.mdx index db34db8e6f..79d642ac4f 100644 --- a/docs/docs/tools-and-integrations/overview.mdx +++ b/docs/docs/tools-and-integrations/overview.mdx @@ -13,13 +13,14 @@ image: https://res.cloudinary.com/djwdcmwdz/image/upload/v1698686403/docs/Blog_T Tracetest can be integrated and used with other tools. See below which integrations we are working on: - ## Integrations - [Cypress](/tools-and-integrations/cypress) is a JavaScript end-to-end testing framework. It is used for testing web applications by simulating user interactions within the browser. Cypress provides a fast, reliable, and easy-to-use testing environment for developers. - [Playwright](/tools-and-integrations/playwright) is an open-source automation framework developed by Microsoft that enables cross-browser automation for web applications. It provides a set of APIs and libraries for automating interactions with web browsers such as Chrome, Firefox, and Microsoft Edge. +- [Typescript](/tools-and-integrations/typescript) is essential for building dynamic web applications on both the frontend and backend, with widespread support across browsers and platforms. Its vibrant ecosystem, community backing, and adaptability make it a cornerstone of modern software development, driving innovation and meeting the evolving demands of the tech industry. + - [Keptn](/tools-and-integrations/keptn) is a powerful tool to automate the lifecycle of your application running on Kubernetes. With this integration, you can run Tracetest alongside Keptn test tasks to validate systems managed by Keptn. - [K6](/tools-and-integrations/k6) is a powerful tool to run load tests against any type of services (REST, GRPC, GraphQL, etc). It is widely used by Developers, Site Reliability Engineers and Software Engineers in Test/QA teams to find potential issues when testing real life scenarios in both controlled environments and production. diff --git a/docs/docs/tools-and-integrations/typescript.mdx b/docs/docs/tools-and-integrations/typescript.mdx new file mode 100644 index 0000000000..970f20bfdf --- /dev/null +++ b/docs/docs/tools-and-integrations/typescript.mdx @@ -0,0 +1,385 @@ +--- +id: typescript +title: Programmatically triggered trace-based tests using Tracetest and Typescript +description: Quickstart on how to use the Tracetest NPM @tracetest/client Typescript package to Programatically trigger Trace-Based Tests. +hide_table_of_contents: false +keywords: + - tracetest + - trace-based testing + - observability + - distributed tracing + - testing + - typescript + - programmatically +image: https://res.cloudinary.com/djwdcmwdz/image/upload/v1698686403/docs/Blog_Thumbnail_14_rsvkmo.jpg +--- + +:::note +[Check out the source code on GitHub here.](https://github.com/kubeshop/tracetest/tree/main/examples/quick-start-typescript) +::: + +[Tracetest](https://app.tracetest.io/) is a testing tool based on [OpenTelemetry](https://opentelemetry.io/) that allows you to test your distributed application. It allows you to use data from distributed traces generated by OpenTelemetry to validate and assert if your application has the desired behavior defined by your test definitions. + +JavaScript/TypeScript is today the most popular language for web development, and it is also the most popular language for writing tests and automation scripts. + +## Why is this important? + +When working with testing tools, the most important thing is to be able to integrate them into your existing workflow and tooling. This is why we have created the `@tracetest/client` NPM package, which allows you to use the Tracetest platform to run trace-based tests from your existing JavaScript/TypeScript code. +Enabling you to run tests at any point in your code, and not only at the end of the test run, allows you to use trace-based testing as a tool to help you develop your application. + +## The `@tracetest/client` NPM Package + +With the [`@tracetest/client` NPM Package](https://www.npmjs.com/package/@tracetest/client), you will unlock the power of OpenTelemetry that allows you to run deeper testing based on the traces and spans generated by each of the checkpoints that you define within your services. + +## How It Works + +The following is a high-level sequence diagram of how the Typescript script, the `@tracetest/client` package, and Tracetest interact. + +```mermaid +sequenceDiagram + Typescript->>+Typescript: Execute tests + Typescript->>+@tracetest/client: Createst instance + @tracetest/client-->>-Typescript: Ok + Typescript->>+Typescript: Run pre-steps + Typescript->>+@tracetest/client: Creates test + @tracetest/client-->>-Typescript: Ok + Typescript->>+@tracetest/client: Runs test + @tracetest/client-->>-Typescript: Ok + Typescript->>@tracetest/client: Waits for results and shows the summary +``` + +## Today You'll Learn How to integrate Trace-Based Tests with your Typescript Code + +This is a simple quick-start guide on how to use the Tracetest `@tracetest/client` NPM package to enhance your Typescript toolkit with trace-based testing. The infrastructure will use the Pokeshop Demo as a testing ground, triggering requests against it and generating telemetry data. + +## Requirements + +**Tracetest Account**: + +- Sign up to [`app.tracetest.io`](https://app.tracetest.io) or follow the [get started](/getting-started/installation) docs. +- Create an [environment](/concepts/environments). +- Create an [environment token](/concepts/environment-tokens). +- Have access to the environment's [agent API key](/configuration/agent). + +**Docker**: Have [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed on your machine. + +## Installing the `@tracetest/client` NPM Package + +Installing the `@tracetest/client` NPM Package is as easy as running the following command: + +```bash +npm i @tracetest/client +``` + +## Using the Tracetest Client Package + +Once you have installed the `@tracetest/client` package, you can import it and start making use of it as any other library to trigger trace-based tests and run checks against the resulting telemetry data. + +## Project Structure + +The project is built with Docker Compose. + +### Pokeshop Demo App + +The [Pokeshop Demo App](/live-examples/pokeshop/overview) is a complete example of a distributed application using different backend and front-end services, implementation code is written in Typescript. + +The `docker-compose-yaml` file in the root directory is for the Pokeshop Demo app, the OpenTelemetry setup, and the [Tracetest Agent](/concepts/agent). + +### Docker Compose Network + +All `services` in the `docker-compose.yaml` are on the same network and will be reachable by hostname from within other services. For example, `jaeger:14250` in the `collector.config.yaml` file will map to the `jaeger` service, where port `14250` is the port where the Jaeger all-in-one instance accepts telemetry data. + +## Tracetest Test Definitions + +The `definitions.ts` file contains the JSON version of the test definitions that will be used to run the tests. It uses the HTTP trigger to execute requests against the Pokeshop Demo. + +```typescript +import { TestResource } from "@tracetest/client/dist/modules/openapi-client"; + +export const importDefinition: TestResource = { + type: "Test", + spec: { + id: "99TOHzpSR", + name: "Typescript: Import a Pokemon", + trigger: { + type: "http", + httpRequest: { + method: "POST", + url: "${var:BASE_URL}/import", + body: '{"id": ${var:POKEMON_ID}}', + headers: [ + { + key: "Content-Type", + value: "application/json", + }, + ], + }, + }, + specs: [ + { + selector: 'span[tracetest.span.type="general" name = "validate request"] span[tracetest.span.type="http"]', + name: "All HTTP Spans: Status code is 200", + assertions: ["attr:http.status_code = 200"], + }, + { + selector: 'span[tracetest.span.type="http" name="GET" http.method="GET"]', + assertions: ['attr:http.route = "/api/v2/pokemon/${var:POKEMON_ID}"'], + }, + { + selector: 'span[tracetest.span.type="database"]', + name: "All Database Spans: Processing time is less than 1s", + assertions: ["attr:tracetest.span.duration < 1s"], + }, + ], + outputs: [ + { + name: "DATABASE_POKEMON_ID", + selector: + 'span[tracetest.span.type="database" name="create pokeshop.pokemon" db.system="postgres" db.name="pokeshop" db.user="ashketchum" db.operation="create" db.sql.table="pokemon"]', + value: "attr:db.result | json_path '$.id'", + }, + ], + }, +}; + +export const deleteDefinition: TestResource = { + type: "Test", + spec: { + id: "C2gwdktIR", + name: "Typescript: Delete a Pokemon", + trigger: { + type: "http", + httpRequest: { + method: "DELETE", + url: "${var:BASE_URL}/${var:POKEMON_ID}", + headers: [ + { + key: "Content-Type", + value: "application/json", + }, + ], + }, + }, + specs: [ + { + selector: + 'span[tracetest.span.type="database" db.system="redis" db.operation="del" db.redis.database_index="0"]', + assertions: ['attr:db.payload = \'{"key":"pokemon-${var:POKEMON_ID}"}\''], + }, + { + selector: + 'span[tracetest.span.type="database" name="delete pokeshop.pokemon" db.system="postgres" db.name="pokeshop" db.user="ashketchum" db.operation="delete" db.sql.table="pokemon"]', + assertions: ["attr:db.result = 1"], + }, + { + selector: 'span[tracetest.span.type="database"]', + name: "All Database Spans: Processing time is less than 100ms", + assertions: ["attr:tracetest.span.duration < 100ms"], + }, + ], + }, +}; +``` + +## Creating the Typescript Script + +The `index.ts` file contains the Typescript script that will be used to trigger requests against the Pokeshop Demo and run trace-based tests. The steps executed by this script are the following: + +1. Import the `@tracetest/client` package. +2. Create a new `Tracetest` instance. +3. Get the last imported Pokemon number from the `GET /pokemon` endpoint using `fetch`. +4. Import the following 5 Pokemon after the last number by triggering a trace-based test to the `POST /import` endpoint. +5. From each test output, get the `DATABASE_POKEMON_ID` value and add it to a list. +6. Delete the imported Pokemon by triggering a trace-based test to the `DELETE /:id` endpoint. + +```typescript +import Tracetest from "@tracetest/client"; +import { config } from "dotenv"; +import { PokemonList } from "./types"; +import { deleteDefinition, importDefinition } from "./definitions"; + +config(); + +const { TRACETEST_API_TOKEN = "", POKESHOP_DEMO_URL = "http://api:8081" } = process.env; + +const baseUrl = `${POKESHOP_DEMO_URL}/pokemon`; + +const main = async () => { + const tracetest = await Tracetest(TRACETEST_API_TOKEN); + + const getLastPokemonId = async (): Promise => { + const response = await fetch(baseUrl); + const list = (await response.json()) as PokemonList; + + return list.items.length + 1; + }; + + // get the initial pokemon from the API + const pokemonId = (await getLastPokemonId()) + 1; + + const getVariables = (id: string) => [ + { key: "POKEMON_ID", value: id }, + { key: "BASE_URL", value: baseUrl }, + ]; + + const importedPokemonList: string[] = []; + + const importPokemons = async (startId: number) => { + const test = await tracetest.newTest(importDefinition); + // imports all pokemons + await Promise.all( + new Array(5).fill(0).map(async (_, index) => { + console.log(`ℹ Importing pokemon ${startId + index + 1}`); + const run = await tracetest.runTest(test, { variables: getVariables(`${startId + index + 1}`) }); + const updatedRun = await run.wait(); + const pokemonId = updatedRun.outputs?.find((output) => output.name === "DATABASE_POKEMON_ID")?.value || ""; + + console.log(`ℹ Adding pokemon ${pokemonId} to the list, ${updatedRun}`); + importedPokemonList.push(pokemonId); + }) + ); + }; + + const deletePokemons = async () => { + const test = await tracetest.newTest(deleteDefinition); + // deletes all pokemons + await Promise.all( + importedPokemonList.map(async (pokemonId) => { + console.log(`ℹ Deleting pokemon ${pokemonId}`); + return tracetest.runTest(test, { variables: getVariables(pokemonId) }); + }) + ); + }; + + await importPokemons(pokemonId); + console.log(await tracetest.getSummary()); + + await deletePokemons(); + console.log(await tracetest.getSummary()); +}; + +main(); +``` + +## Tracetest Agent Connects to Tracetest + +The `docker-compose.yaml` file includes the [Tracetest Agent](/concepts/agent) image, which is going to be listening for incoming traces from both the gRPC (4317) and HTTP (4318) OTLP ports. + +```yaml +version: "3" + +services: + tracetest-agent: + image: kubeshop/tracetest-agent:latest + environment: + TRACETEST_DEV: ${TRACETEST_DEV} + TRACETEST_API_KEY: ${TRACETEST_API_KEY} +``` + +Do not forget to set the `TRACETEST_AGENT_API_KEY` environment variable in the `.env` file to your Tracetest Agent. The API key available on the `Settings > Agent` page of your environment as you can see [here](/configuration/agent). + +From the `collector.config.yaml` file we can see how the telemetry data is routed to the `tracetest-agent` service. + +```yaml title=tracetest-datastore.yaml +receivers: + otlp: + protocols: + grpc: + http: + cors: + allowed_origins: + - "http://*" + - "https://*" + +processors: + batch: + +exporters: + logging: + loglevel: debug + jaeger: + endpoint: ${JAEGER_ENDPOINT} + tls: + insecure: true + otlp/trace: + endpoint: tracetest-agent:4317 + tls: + insecure: true + +service: + pipelines: + traces: + receivers: [otlp] + processors: [] + exporters: [logging, jaeger] + traces/1: + receivers: [otlp] + processors: [batch] + exporters: [otlp/trace] +``` + +## Running the Full Example + +To start the full setup, run the following command: + +```bash +docker-compose up +``` + +You can visit the Pokeshop Demo UI at `http://localhost:8081/`. + +## Finding the Results + +The output from the Typescript script should be visible in the terminal. Where you can find links to Tracetest to find details about trace-based test results. + +```bash +2024-01-26 10:54:44 ℹ Importing pokemon 3 +2024-01-26 10:54:44 ℹ Importing pokemon 4 +2024-01-26 10:54:44 ℹ Importing pokemon 5 +2024-01-26 10:54:44 ℹ Importing pokemon 6 +2024-01-26 10:54:44 ℹ Importing pokemon 7 +2024-01-26 10:54:56 ℹ Adding pokemon 1 to the list +2024-01-26 10:54:58 ℹ Adding pokemon 2 to the list +2024-01-26 10:54:59 ℹ Adding pokemon 3 to the list +2024-01-26 10:54:59 ℹ Adding pokemon 5 to the list +2024-01-26 10:54:59 ℹ Adding pokemon 4 to the list +2024-01-26 10:54:59 +2024-01-26 10:54:59 Successful: 5 +2024-01-26 10:54:59 Failed: 0 +2024-01-26 10:54:59 +2024-01-26 10:54:59 [✔️ Typescript: Import a Pokemon] #1 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/1 +2024-01-26 10:54:59 [✔️ Typescript: Import a Pokemon] #2 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/2 +2024-01-26 10:54:59 [✔️ Typescript: Import a Pokemon] #3 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/3 +2024-01-26 10:54:59 [✔️ Typescript: Import a Pokemon] #4 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/4 +2024-01-26 10:54:59 [✔️ Typescript: Import a Pokemon] #5 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/5 +2024-01-26 10:54:59 +2024-01-26 10:54:59 ℹ Deleting pokemon 1 +2024-01-26 10:54:59 ℹ Deleting pokemon 2 +2024-01-26 10:54:59 ℹ Deleting pokemon 3 +2024-01-26 10:54:59 ℹ Deleting pokemon 5 +2024-01-26 10:54:59 ℹ Deleting pokemon 4 +2024-01-26 10:55:14 +2024-01-26 10:55:14 Successful: 10 +2024-01-26 10:55:14 Failed: 0 +2024-01-26 10:55:14 +2024-01-26 10:55:14 [✔️ Typescript: Import a Pokemon] #1 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/1 +2024-01-26 10:55:14 [✔️ Typescript: Import a Pokemon] #2 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/2 +2024-01-26 10:55:14 [✔️ Typescript: Import a Pokemon] #3 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/3 +2024-01-26 10:55:14 [✔️ Typescript: Import a Pokemon] #4 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/4 +2024-01-26 10:55:14 [✔️ Typescript: Import a Pokemon] #5 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/99TOHzpSR/run/5 +2024-01-26 10:55:14 [✔️ Typescript: Delete a Pokemon] #1 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/C2gwdktIR/run/1 +2024-01-26 10:55:14 [✔️ Typescript: Delete a Pokemon] #2 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/C2gwdktIR/run/2 +2024-01-26 10:55:14 [✔️ Typescript: Delete a Pokemon] #4 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/C2gwdktIR/run/4 +2024-01-26 10:55:14 [✔️ Typescript: Delete a Pokemon] #3 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/C2gwdktIR/run/3 +2024-01-26 10:55:14 [✔️ Typescript: Delete a Pokemon] #5 - https://app-stage.tracetest.io/organizations/ttorg_08eb62e60d1db492/environments/ttenv_70f346fe8ddba633/test/C2gwdktIR/run/5 +``` + +## What's Next? + +After running the initial set of tests, you can click the run link for any of them, update the assertions, and run the scripts once more. This flow enables complete a trace-based TDD flow. + +![assertions](./img/tracetest-cloud-typescript.gif) + +## Learn More + +Please visit our [examples in GitHub](https://github.com/kubeshop/tracetest/tree/main/examples) and join our [Slack Community](https://dub.sh/tracetest-community) for more info! diff --git a/docs/sidebars.js b/docs/sidebars.js index e5be5021de..0886c278a1 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -358,6 +358,11 @@ const sidebars = { id: "tools-and-integrations/playwright", label: "Playwright", }, + { + type: "doc", + id: "tools-and-integrations/typescript", + label: "Typescript", + }, { type: "doc", id: "tools-and-integrations/keptn", diff --git a/examples/observability-driven-development-go-tracetest/bookstore/part1/config.yml b/examples/observability-driven-development-go-tracetest/bookstore/part1/config.yml deleted file mode 100755 index 2528a5dc62..0000000000 --- a/examples/observability-driven-development-go-tracetest/bookstore/part1/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -scheme: http -endpoint: localhost:11633 -analyticsEnabled: true diff --git a/examples/observability-driven-development-go-tracetest/bookstore/part2.1/config.yml b/examples/observability-driven-development-go-tracetest/bookstore/part2.1/config.yml deleted file mode 100755 index 2528a5dc62..0000000000 --- a/examples/observability-driven-development-go-tracetest/bookstore/part2.1/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -scheme: http -endpoint: localhost:11633 -analyticsEnabled: true diff --git a/examples/observability-driven-development-go-tracetest/bookstore/part2.2/config.yml b/examples/observability-driven-development-go-tracetest/bookstore/part2.2/config.yml deleted file mode 100755 index 2528a5dc62..0000000000 --- a/examples/observability-driven-development-go-tracetest/bookstore/part2.2/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -scheme: http -endpoint: localhost:11633 -analyticsEnabled: true diff --git a/examples/observability-driven-development-go-tracetest/bookstore/part3.1/config.yml b/examples/observability-driven-development-go-tracetest/bookstore/part3.1/config.yml deleted file mode 100755 index 2528a5dc62..0000000000 --- a/examples/observability-driven-development-go-tracetest/bookstore/part3.1/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -scheme: http -endpoint: localhost:11633 -analyticsEnabled: true diff --git a/examples/observability-driven-development-go-tracetest/bookstore/part3.2/config.yml b/examples/observability-driven-development-go-tracetest/bookstore/part3.2/config.yml deleted file mode 100755 index 2528a5dc62..0000000000 --- a/examples/observability-driven-development-go-tracetest/bookstore/part3.2/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -scheme: http -endpoint: localhost:11633 -analyticsEnabled: true diff --git a/examples/quick-start-nodejs-manual-instrumentation/config.yml b/examples/quick-start-nodejs-manual-instrumentation/config.yml deleted file mode 100755 index cc15f11ad5..0000000000 --- a/examples/quick-start-nodejs-manual-instrumentation/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -scheme: http -endpoint: localhost:11633 -analyticsEnabled: false diff --git a/examples/quick-start-typescript/.env.template b/examples/quick-start-typescript/.env.template new file mode 100644 index 0000000000..15db539659 --- /dev/null +++ b/examples/quick-start-typescript/.env.template @@ -0,0 +1,3 @@ +TRACETEST_API_TOKEN= +POKESHOP_DEMO_URL=http://localhost:8081 +TRACETEST_AGENT_API_KEY= diff --git a/examples/quick-start-typescript/.gitignore b/examples/quick-start-typescript/.gitignore new file mode 100644 index 0000000000..8f00ef2307 --- /dev/null +++ b/examples/quick-start-typescript/.gitignore @@ -0,0 +1,3 @@ +node_modules +.env +dist \ No newline at end of file diff --git a/examples/quick-start-typescript/.prettierrc b/examples/quick-start-typescript/.prettierrc new file mode 100644 index 0000000000..544138be45 --- /dev/null +++ b/examples/quick-start-typescript/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/examples/quick-start-typescript/Dockerfile b/examples/quick-start-typescript/Dockerfile new file mode 100644 index 0000000000..8189c7f968 --- /dev/null +++ b/examples/quick-start-typescript/Dockerfile @@ -0,0 +1,31 @@ +FROM node as builder + +# Create app directory +WORKDIR /usr/src/app + +# Install app dependencies +COPY package*.json ./ + +RUN npm ci + +COPY . . + +RUN npm run build + +FROM node:slim + +ENV NODE_ENV production +USER node + +# Create app directory +WORKDIR /usr/src/app + +# Install app dependencies +COPY package*.json ./ + +RUN npm ci --production + +COPY --from=builder /usr/src/app/dist ./dist + +EXPOSE 8080 +CMD [ "node", "dist/index.js" ] diff --git a/examples/quick-start-typescript/README.md b/examples/quick-start-typescript/README.md new file mode 100644 index 0000000000..a263c401a3 --- /dev/null +++ b/examples/quick-start-typescript/README.md @@ -0,0 +1,14 @@ +# Tracetest + Typescript (using @tracetest/client NPM Package) + +> [Read the detailed recipe for setting up Tracetest + Typescript (using @tracetest/client NPM Package) in our documentation.](https://docs.tracetest.io/tools-and-integrations/typescript) + +This repository's objective is to show how you can run trace-based tests from your Javascript/Typescript environment, including setup stages and waiting for results to be ready. + +## Steps + +1. Copy the `.env.template` file to `.env`. +2. Log into the [Tracetest app](https://app.tracetest.io/). +3. Fill out the [token](https://docs.tracetest.io/concepts/environment-tokens) and [agent API key](https://docs.tracetest.io/concepts/agent) details. +4. Run `docker compose up -d`. +5. Look for the `tracetest-client` service logs to find out the results from the trace-based tests. +6. Follow the links to the [Tracetest app](https://app.tracetest.io/) to find more details. diff --git a/examples/quick-start-typescript/collector.config.yaml b/examples/quick-start-typescript/collector.config.yaml new file mode 100644 index 0000000000..56512aeae9 --- /dev/null +++ b/examples/quick-start-typescript/collector.config.yaml @@ -0,0 +1,35 @@ +receivers: + otlp: + protocols: + grpc: + http: + cors: + allowed_origins: + - "http://*" + - "https://*" + +processors: + batch: + +exporters: + logging: + loglevel: debug + jaeger: + endpoint: ${JAEGER_ENDPOINT} + tls: + insecure: true + otlp/trace: + endpoint: tracetest-agent:4317 + tls: + insecure: true + +service: + pipelines: + traces: + receivers: [otlp] + processors: [] + exporters: [logging, jaeger] + traces/1: + receivers: [otlp] + processors: [batch] + exporters: [otlp/trace] diff --git a/examples/quick-start-typescript/definitions.ts b/examples/quick-start-typescript/definitions.ts new file mode 100644 index 0000000000..bd7c83ed73 --- /dev/null +++ b/examples/quick-start-typescript/definitions.ts @@ -0,0 +1,85 @@ +import { TestResource } from '@tracetest/client/dist/modules/openapi-client'; + +export const importDefinition: TestResource = { + type: 'Test', + spec: { + id: '99TOHzpSR', + name: 'Typescript: Import a Pokemon', + trigger: { + type: 'http', + httpRequest: { + method: 'POST', + url: '${var:BASE_URL}/import', + body: '{"id": ${var:POKEMON_ID}}', + headers: [ + { + key: 'Content-Type', + value: 'application/json', + }, + ], + }, + }, + specs: [ + { + selector: 'span[tracetest.span.type="general" name = "validate request"] span[tracetest.span.type="http"]', + name: 'All HTTP Spans: Status code is 200', + assertions: ['attr:http.status_code = 200'], + }, + { + selector: 'span[tracetest.span.type="http" name="GET" http.method="GET"]', + assertions: ['attr:http.route = "/api/v2/pokemon/${var:POKEMON_ID}"'], + }, + { + selector: 'span[tracetest.span.type="database"]', + name: 'All Database Spans: Processing time is less than 1s', + assertions: ['attr:tracetest.span.duration < 1s'], + }, + ], + outputs: [ + { + name: 'DATABASE_POKEMON_ID', + selector: + 'span[tracetest.span.type="database" name="create postgres.pokemon" db.system="postgres" db.name="postgres" db.user="postgres" db.operation="create" db.sql.table="pokemon"]', + value: "attr:db.result | json_path '$.id'", + }, + ], + }, +}; + +export const deleteDefinition: TestResource = { + type: 'Test', + spec: { + id: 'C2gwdktIR', + name: 'Typescript: Delete a Pokemon', + trigger: { + type: 'http', + httpRequest: { + method: 'DELETE', + url: '${var:BASE_URL}/${var:POKEMON_ID}', + headers: [ + { + key: 'Content-Type', + value: 'application/json', + }, + ], + }, + }, + specs: [ + { + selector: + 'span[tracetest.span.type="database" db.system="redis" db.operation="del" db.redis.database_index="0"]', + assertions: ['attr:db.payload = \'{"key":"pokemon-${var:POKEMON_ID}"}\''], + }, + { + selector: + 'span[tracetest.span.type="database" name="delete postgres.pokemon" db.system="postgres" db.name="postgres" db.user="postgres" db.operation="delete" db.sql.table="pokemon"]', + assertions: ['attr:db.result = 1'], + }, + { + selector: 'span[tracetest.span.type="database"]', + name: 'All Database Spans: Processing time is less than 100ms', + assertions: ['attr:tracetest.span.duration < 100ms'], + }, + ], + }, +}; diff --git a/examples/quick-start-typescript/docker-compose.yaml b/examples/quick-start-typescript/docker-compose.yaml new file mode 100644 index 0000000000..0ad3c45a14 --- /dev/null +++ b/examples/quick-start-typescript/docker-compose.yaml @@ -0,0 +1,163 @@ +version: '3.5' +name: pokeshop + +services: + # tracetest services + tracetest-client: + build: . + environment: + TRACETEST_API_TOKEN: ${TRACETEST_API_TOKEN} + POKESHOP_DEMO_URL: ${POKESHOP_DEMO_URL} + depends_on: + api: + condition: service_healthy + tracetest-agent: + environment: + TRACETEST_DEV: ${TRACETEST_DEV} + TRACETEST_API_KEY: ${TRACETEST_AGENT_API_KEY} + TRACETEST_SERVER_URL: ${TRACETEST_SERVER_URL} + image: kubeshop/tracetest-agent:latest + networks: + default: null + + # pokeshop demo services + postgres: + image: postgres:14 + ports: + - 5434:5432 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB'] + interval: 1s + timeout: 5s + retries: 60 + + cache: + image: redis:6 + ports: + - 6379:6379 + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 1s + timeout: 3s + retries: 60 + + queue: + image: rabbitmq:3.12 + restart: unless-stopped + ports: + - 5672:5672 + - 15672:15672 + healthcheck: + test: rabbitmq-diagnostics -q check_running + interval: 1s + timeout: 5s + retries: 60 + + jaeger: + image: jaegertracing/all-in-one:latest + ports: + - 14250:14250 + - 16685:16685 + - 16686:16686 + environment: + - COLLECTOR_ZIPKIN_HOST_PORT=:9411 + - COLLECTOR_OTLP_ENABLED=true + healthcheck: + test: ['CMD', 'wget', '--spider', 'localhost:16686'] + interval: 1s + timeout: 3s + retries: 60 + + otel-collector: + image: otel/opentelemetry-collector-contrib:0.59.0 + restart: unless-stopped + extra_hosts: + - 'host.docker.internal:host-gateway' + ports: + - 55679:55679 + - 8888:8888 + - 4317:4317 + - 4318:4318 + command: + - '--config' + - '/otel-local-config.yaml' + volumes: + - ./collector.config.yaml:/otel-local-config.yaml + environment: + - JAEGER_ENDPOINT=jaeger:14250 + depends_on: + jaeger: + condition: service_healthy + + api: + image: kubeshop/demo-pokemon-api:latest + restart: unless-stopped + pull_policy: always + environment: + REDIS_URL: cache + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?schema=public + RABBITMQ_HOST: queue + POKE_API_BASE_URL: https://pokeapi.co/api/v2 + COLLECTOR_ENDPOINT: http://otel-collector:4317 + NPM_RUN_COMMAND: api + healthcheck: + test: ['CMD', 'wget', '--spider', 'localhost:8081'] + interval: 1s + timeout: 3s + retries: 60 + ports: + - 8081:8081 + depends_on: + postgres: + condition: service_healthy + cache: + condition: service_healthy + queue: + condition: service_healthy + + worker: + image: kubeshop/demo-pokemon-api:latest + restart: unless-stopped + pull_policy: always + environment: + REDIS_URL: cache + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?schema=public + RABBITMQ_HOST: queue + POKE_API_BASE_URL: https://pokeapi.co/api/v2 + COLLECTOR_ENDPOINT: http://otel-collector:4317 + NPM_RUN_COMMAND: worker + depends_on: + postgres: + condition: service_healthy + cache: + condition: service_healthy + queue: + condition: service_healthy + + rpc: + image: kubeshop/demo-pokemon-api:latest + restart: unless-stopped + pull_policy: always + environment: + REDIS_URL: cache + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?schema=public + RABBITMQ_HOST: queue + POKE_API_BASE_URL: https://pokeapi.co/api/v2 + COLLECTOR_ENDPOINT: http://otel-collector:4317 + NPM_RUN_COMMAND: rpc + healthcheck: + test: ['CMD', 'lsof', '-i', '8082'] + interval: 1s + timeout: 3s + retries: 60 + depends_on: + postgres: + condition: service_healthy + cache: + condition: service_healthy + queue: + condition: service_healthy diff --git a/examples/quick-start-typescript/index.ts b/examples/quick-start-typescript/index.ts new file mode 100644 index 0000000000..f28a31b53a --- /dev/null +++ b/examples/quick-start-typescript/index.ts @@ -0,0 +1,67 @@ +import Tracetest from '@tracetest/client'; +import { config } from 'dotenv'; +import { PokemonList } from './types'; +import { deleteDefinition, importDefinition } from './definitions'; + +config(); + +const { TRACETEST_API_TOKEN = '', POKESHOP_DEMO_URL = 'http://api:8081' } = process.env; + +const baseUrl = `${POKESHOP_DEMO_URL}/pokemon`; + +const main = async () => { + const tracetest = await Tracetest(TRACETEST_API_TOKEN); + + const getLastPokemonId = async (): Promise => { + const response = await fetch(baseUrl); + const list = (await response.json()) as PokemonList; + + return list.items.length + 1; + }; + + // get the initial pokemon from the API + const pokemonId = (await getLastPokemonId()) + 1; + + const getVariables = (id: string) => [ + { key: 'POKEMON_ID', value: id }, + { key: 'BASE_URL', value: baseUrl }, + ]; + + const importedPokemonList: string[] = []; + + const importPokemons = async (startId: number) => { + const test = await tracetest.newTest(importDefinition); + // imports all pokemons + await Promise.all( + new Array(5).fill(0).map(async (_, index) => { + console.log(`ℹ Importing pokemon ${startId + index + 1}`); + const run = await tracetest.runTest(test, { variables: getVariables(`${startId + index + 1}`) }); + const updatedRun = await run.wait(); + const pokemonId = updatedRun.outputs?.find((output) => output.name === 'DATABASE_POKEMON_ID')?.value || ''; + + console.log(`ℹ Adding pokemon ${pokemonId} to the list`); + importedPokemonList.push(pokemonId); + }) + ); + }; + + const deletePokemons = async () => { + const test = await tracetest.newTest(deleteDefinition); + // deletes all pokemons + await Promise.all( + importedPokemonList.map(async (pokemonId) => { + console.log(`ℹ Deleting pokemon ${pokemonId}`); + const run = await tracetest.runTest(test, { variables: getVariables(pokemonId) }); + run.wait(); + }) + ); + }; + + await importPokemons(pokemonId); + console.log(await tracetest.getSummary()); + + await deletePokemons(); + console.log(await tracetest.getSummary()); +}; + +main(); diff --git a/examples/quick-start-typescript/package-lock.json b/examples/quick-start-typescript/package-lock.json new file mode 100644 index 0000000000..f1ffe6f9a0 --- /dev/null +++ b/examples/quick-start-typescript/package-lock.json @@ -0,0 +1,294 @@ +{ + "name": "quick-start-typescript", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "quick-start-typescript", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@tracetest/client": "^0.0.27", + "dotenv": "^16.3.2" + }, + "devDependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.7.0", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.20.0", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "1.20.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.8.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.20.0", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "1.20.0", + "@opentelemetry/semantic-conventions": "1.20.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.8.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.20.0", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "1.20.0", + "@opentelemetry/resources": "1.20.0", + "@opentelemetry/semantic-conventions": "1.20.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.8.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.20.0", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@tracetest/client": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/@tracetest/client/-/client-0.0.27.tgz", + "integrity": "sha512-JNO7AjPGoFie+egYyr7ZZgNVw9X59PIBvvMuhifd0Ofl1JOlXkPtXTM5WgocToZ6Pnmuk/zQEdVanFRt7r886g==", + "dependencies": { + "js-yaml": "^4.1.0" + }, + "peerDependencies": { + "@opentelemetry/sdk-trace-base": "^1.18.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.11.6", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/create-require": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.3.2", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/examples/quick-start-typescript/package.json b/examples/quick-start-typescript/package.json new file mode 100644 index 0000000000..5cb191d7f1 --- /dev/null +++ b/examples/quick-start-typescript/package.json @@ -0,0 +1,25 @@ +{ + "name": "quick-start-typescript", + "version": "0.0.1", + "description": "Tracetest x Typescript example", + "main": "index.ts", + "scripts": { + "start": "ts-node index.ts", + "build": "tsc" + }, + "keywords": [ + "tracetest", + "typescript", + "@tracetest/core" + ], + "author": "", + "license": "ISC", + "devDependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "dependencies": { + "@tracetest/client": "^0.0.27", + "dotenv": "^16.3.2" + } +} diff --git a/examples/quick-start-typescript/tsconfig.json b/examples/quick-start-typescript/tsconfig.json new file mode 100644 index 0000000000..fd70902d6d --- /dev/null +++ b/examples/quick-start-typescript/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/examples/quick-start-typescript/types.ts b/examples/quick-start-typescript/types.ts new file mode 100644 index 0000000000..ff5dcdf437 --- /dev/null +++ b/examples/quick-start-typescript/types.ts @@ -0,0 +1,12 @@ +export type PokemonList = { + items: Pokemon[]; + totalCount: number; +}; + +export type Pokemon = { + id: number; + imageUrl: string; + isFeatured: boolean; + type: string; + name: string; +}; diff --git a/server/external/jaeger-idl b/server/external/jaeger-idl deleted file mode 160000 index ca9d21aa7c..0000000000 --- a/server/external/jaeger-idl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ca9d21aa7c2f1cabe00f6ac844ca10def9053923 diff --git a/server/external/opentelemetry-proto b/server/external/opentelemetry-proto deleted file mode 160000 index c444081593..0000000000 --- a/server/external/opentelemetry-proto +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c444081593837fe770c825a83c864de736a768b4