How to Use

March 9, 2025 · View on GitHub

Documentation for Usage of Echoed.

Table of contents

Jest

YAML

You can write tests using YAML, and Echoed will convert them into Jest tests.
Compilation flow

Create Observable Tests

The YAML below makes a request to http://localhost:8080/api/cart and validates the response.

variable:
  productId: OLJCESPC7Z
scenarios:
  - name: Get product detail
    steps:
      - description: fetch /products/{id}
        act:
          runner: fetch
          argument:
            endpoint: /products/${productId}
        assert:
          - expect(_.jsonBody.id).toBe(productId)

To execute the test, use the command npx echoed compile to transform the YAML into TypeScript, and then run Jest

Configuration

To configure runners and adjust other options, include scenario block in the .echoed.yml file, like below:

scenario:
  compile:
    env:
      BASE_ENDPOINT: http://localhost:8080
    plugin:
      runner:
        - name: fetch
          module: echoed/scenario/gen/jest/runner
          option:
            baseEndpoint: ${_env.BASE_ENDPOINT}/api
            headers:
              content-type: application/json

For more details, refer to the YAML documentation.

TypeScript

You can write tests in TypeScript too.

Make Tests Observable

To generate an HTML report visualizing API traces, no additional code is needed.
Simply write your Jest tests as usual.

describe("Awesome test", () => {
  it("should pass", async () => {
    const response = await fetch(`http://localhost:8080/api/cart`);
    expect(response.status).toBe(200);

    const body = await response.json();
    expect(body.items.length).toBe(0);
  });
});

The code above produces an HTML report illustrating a trace for the requested endpoint (http://localhost:8080/api/cart).

Test OpenTelemetry's Spans

In addition to the HTML output, Echoed offers a method for testing OpenTelemetry spans.
Use the waitForSpan function to obtain a span that matches your needs.

describe("Awesome test", () => {
  it("should create an OpenTelemetry gRPC span", async () => {
    const response = await fetch(`http://localhost:8080/api/products`);
    expect(response.status).toBe(200);

    const span = await waitForSpan(response, {
      name: "oteldemo.ProductCatalogService/ListProducts",
      resource: {
        attributes: {
          "service.name": "productcatalogservice",
        },
      },
      attributes: {
        "app.products.count": gte(5),
        "rpc.system": /grpc/,
      }
    });
    
    const productsCount = span.attributes.find(attr => attr.key === "app.products.count");
    expect(productsCount?.value?.intValue).toBe(10);
  });
});

The code above waits for a span that satisfies the following specified conditions and then compares it using the expect statement:

  • name is oteldemo.ProductCatalogService/ListProducts
  • service.name in resource is productcatalogservice
  • app.products.count attribute is greater than or equal to 5
  • rpc.system attribute matches /grpc/

Test SQL

You can use the waitForSpan function to test executed SQL too.

describe("Awesome test", () => {
  it("should create an OpenTelemetry span", async () => {
    const response = await fetch(`http://localhost:8080/api/products`, {
      method: "POST",
      body: JSON.stringify({
          name: "Awesome Product",
          price: 100,
      }),
    });
    expect(response.status).toBe(200);

    const span = await waitForSpan(response, {
      name: "oteldemo.ProductCatalogService/CreateProducts",
      resource: {
        attributes: {
          "service.name": "productcatalogservice",
        },
      },
      attributes: {
        "db.system": "postgresql",
        "db.statement": /INSERT INTO products +/,
      }
    });
    
    const query = span.attributes.find(attr => attr.key === "db.statement");
    expect(query?.value?.stringValue).toBe("INSERT INTO products (name, price) VALUES ('Awesome Product', 100)");
  });
});

More Examples

For more examples, refer to jest/example/test directory.

Using Echoed without OpenTelemetry

While Echoed's primary feature is to troubleshoot or analyze tests by visualizing OpenTelemetry data, it can also be used to write Jest tests in YAML.

To add YAML tests into existing Jest tests, simply create a .echoed.yml file for configuration.
In the Installation, you may see nodeEnvironment and reporter is added in jest.config.js. However, because these configurations are to collect OpenTelemetry data, when you don't use OpenTelemetry, there's no need to modify it.

Alternatively, if you wish to create example tests without OpenTelemetry, you can do so using the following commands:

# Create example tests
npm create echoed@latest -- --template jest-no-otel

# Compile YAML to TypeScript and run tests
npx echoed compile
npx jest

Playwright

YAML

You can write tests using YAML, and Echoed will convert them into Playwright tests.
Compilation flow

Create Observable Tests

The YAML below opens http://localhost:8080 and validates DOM elements.

scenarios:
  - name: Validate Homepage
    fixtures:
      - page
    steps:
      - description: Check product list is shown
        act:
          raw: await page.goto("http://localhost:8080")
        assert:
          - expectToBeVisible: "[data-cy=home-page]"
          - expectToHaveCount:
              selector: "[data-cy=product-list] [data-cy=product-card]"
              count: 10

To execute the test, use the command npx echoed compile to transform the YAML into TypeScript, and then run Playwright

Configuration

To configure runners and adjust other options, include scenario block in the .echoed.yml file, like below:

scenario:
  compile:
    targets:
      - yamlDir: test/scenario
        outDir: test/scenario_gen
        type: playwright
    env:
      BASE_ENDPOINT: http://localhost:8080

For more details, refer to the YAML documentation.

TypeScript

You can write tests in TypeScript too.

Make Tests Observable

To generate an HTML report visualizing API traces, replace test of Playwright to Echoed's to intercept requests. For instance, you can use test as below:

// import { test } from "@playwright/test"; <- Replace this line
import { test } from "echoed/playwright/test";

test("opens home page", async ({ page }) => {
  await page.goto("http://localhost:8080/");
  await expect(page).toHaveTitle("OTel demo");

  const productList = page.locator("[data-cy=product-card]");
  await expect(productList).toHaveCount(10);
});

The code above produces an HTML report illustrating traces made when opening the home page(http://localhost:8080).

Test OpenTelemetry's Spans

In addition to the HTML output, Echoed offers a method for testing OpenTelemetry spans.
Use the waitForSpanCreatedIn function to obtain a span that matches your needs.

test("creates an OpenTelemetry gRPC span", async ({ page }) => {
  await page.goto("http://localhost:8080/");
  await expect(page).toHaveTitle("OTel demo");

  const span = await waitForSpanCreatedIn(
    page.context(),
    "http://localhost:8080/api/products",
    {
      name: "oteldemo.ProductCatalogService/ListProducts",
      resource: {
        attributes: {
          "service.name": "productcatalogservice",
        },
      },
      attributes: {
        "app.products.count": gte(5),
        "rpc.system": /grpc/,
      }
    },
  );
  
  const rpcSystem = span.attributes.find(
    (attr) => attr.key === "app.products.count",
  );
  expect(rpcSystem?.value?.intValue).toBe(10);
});

The code above waits for a span that satisfies the following specified conditions and then compares it using the expect statement:

  • name is oteldemo.ProductCatalogService/ListProducts
  • service.name in resource is productcatalogservice
  • app.products.count attribute is greater than or equal to 5
  • rpc.system attribute matches /grpc/

You can also use waitForSpan family functions to test OpenTelemetry spans with the almost same way.

  • waitForSpan is for fetch (globalThis.fetch)
  • waitForSpanFromPlaywrightFetch is for context.request.get or request.get
  • (waitForSpanCreatedIn is for traces created in context)

Test SQL

You can use the waitForSpan function to test executed SQL too.

test("creates an OpenTelemetry span", async ({ request }) => {
  const response = await request.post(`http://localhost:8080/api/products`, {
    data: {
        name: "Awesome Product",
        price: 100,
    },
  });
  expect(response.status()).toBe(200);

  const span = await waitForSpanFromPlaywrightFetch(response, {
    name: "oteldemo.ProductCatalogService/CreateProducts",
    resource: {
      attributes: {
        "service.name": "productcatalogservice",
      },
    },
    attributes: {
      "db.system": "postgresql",
      "db.statement": /INSERT INTO products +/,
    }
  });

  const query = span.attributes.find(attr => attr.key === "db.statement");
  expect(query?.value?.stringValue).toBe("INSERT INTO products (name, price) VALUES ('Awesome Product', 100)");
});

More Examples

For more examples, refer to playwright/example/test directory.

Using Echoed without OpenTelemetry

While Echoed's primary feature is to troubleshoot or analyze tests by visualizing OpenTelemetry data, it can also be used to write Playwright tests in YAML.

To add YAML tests into existing Jest tests, simply create a .echoed.yml file for configuration.
In the Installation, you may see globalSetup and reporter is added in playwright.config.ts. However, because these configurations are to collect OpenTelemetry data, when you don't use OpenTelemetry, there's no need to modify it.

Alternatively, if you wish to create example tests without OpenTelemetry, you can do so using the following commands:

# Create example tests
npm create echoed@latest -- --template playwright-no-otel

# Compile YAML to TypeScript and run tests
npx echoed compile
npx playwright test

Cypress

Make Tests Observable

To generate an HTML report visualizing API traces, no additional code is needed.
Simply write your Cypress tests as usual.

it("opens home page", () => {
  cy.visit("http://localhost:8080");
  cy.title().should("eq", "OTel demo");
  
  cy.get("[data-cy=product-card]").should("have.length", 10);
});

The code above produces an HTML report illustrating traces made when opening the home page(http://localhost:8080).

Test OpenTelemetry's Spans

In addition to the HTML output, Echoed offers a method for testing OpenTelemetry spans.
Use the waitForSpan command to obtain a span that matches your needs.

it("creates an OpenTelemetry gRPC span", () => {
  cy.visit("http://localhost:8080");
  cy.title().should("eq", "OTel demo");

  cy.waitForSpan(
    "http://localhost:8080/api/products",
    {
      name: "oteldemo.ProductCatalogService/ListProducts",
      resource: {
        attributes: {
          "service.name": "productcatalogservice",
        },
      },
      attributes: {
        "app.products.count": gte(5),
        "rpc.system": /grpc/,
      }
    },
  ).then((span) => {
    const rpcSystem = span.attributes.find(
      (attr) => attr.key === "app.products.count",
    );
    expect(rpcSystem?.value?.intValue).to.eq(10);
  });
});

The code above waits for a span that satisfies the following specified conditions and then compares it using the expect statement:

  • name is oteldemo.ProductCatalogService/ListProducts
  • service.name in resource is productcatalogservice
  • app.products.count attribute is greater than or equal to 5
  • rpc.system attribute matches /grpc/

You can also use waitForSpan command to test the response of cy.request() by replacing the url in arguments with the response of cy.request().

Test SQL

You can use the waitForSpan command to test executed SQL too.

it("creates an OpenTelemetry span", () => {
  cy.request("POST", `http://localhost:8080/api/products`, {
    data: {
        name: "Awesome Product",
        price: 100,
    },
  }).then((response) => {
    expect(response.status).to.eq(200);
    
    cy.waitForSpan(response, {
      name: "oteldemo.ProductCatalogService/CreateProducts",
      resource: {
        attributes: {
          "service.name": "productcatalogservice",
        },
      },
      attributes: {
        "db.system": "postgresql",
        "db.statement": /INSERT INTO products +/,
      }
    }).then((span) => {
      const query = span.attributes.find(attr => attr.key === "db.statement");
      expect(query?.value?.stringValue).to.eq("INSERT INTO products (name, price) VALUES ('Awesome Product', 100)");
    });
  });
});

YAML

YAML is under development. 🚧

More Examples

For more examples, refer to cypress/example/test directory.

Analyze Coverage

You can get coverage of your HTTP and gRPC endpoints based on OpenAPI or Protocol Buffers specifications.
By configuring the openapi or proto option in your .echoed.yml file, Echoed analyzes the coverage of your tests and generates a report.
For more option, refer to the Configuration.

services:
  - name: frontend
    namespace: opentelemetry-demo
    openapi: "./example/echoed-opentelemetry-demo/src/frontend/schema.yaml"
  - name: cartservice
    namespace: opentelemetry-demo
    proto:
      filePath: "./example/echoed-opentelemetry-demo/pb/demo.proto"
      services:
        - oteldemo.CartService