Modern applications, built with distributed architectures and microservices, pose unique challenges for quality assurance (QA) engineers. Traditional testing methods, often focused on individual components, may not adequately capture the complex interplay between these services. Trace-based testing emerges as a potential solution, offering a way to analyze the entire request lifecycle and identify issues that traditional methods might miss.
Trace-based testing leverages data captured during an application's execution, known as a distributed trace. This trace acts as a record of every interaction across various services involved in processing a user request. By analyzing these traces, QA engineers can gain insights into the application's behavior at each step, potentially leading to more comprehensive testing strategies.
This blog explores trace-based testing and its potential benefits for QA automation. We will also delve into Tracetest, a tool specifically designed for this type of testing, and evaluate its features and functionalities relevant to QA engineers.
The core appeal of trace-based testing for QA automation lies in its ability to address the limitations of traditional methods when dealing with complex applications. Let's explore some of the potential benefits:
Tracetest offers functionalities specifically designed to streamline trace-based testing workflows for QA engineers. Let's delve into some key features relevant to automation:
Selectors and Checks:
These form the foundation of defining assertions within Tracetest. Selectors allow engineers to target specific parts of a trace, filtering the data based on criteria like service name, operation type, or specific attributes. Checks are then used to verify the extracted data against expected values. This granular control allows for precise assertions against the application's behavior as reflected in the trace.
span[tracetest.span.type="http"] // filter all spans of type http
span[tracetest.span.type="http" service.name="test-api"] // select all http spans AND spans that were created by test-api service
span[service.name contains "api"] // selects spans with "api" in service name
span[tracetest.span.type="http"]:first // select spans by pseudo-classes
Span Signatures:
Manually defining selectors for every test can be cumbersome. Tracetest offers the concept of span signatures, which automatically generate a selector based on a combination of attributes within a specific span. This can simplify test creation, especially for frequently occurring patterns within traces.
Span Signature Example
Polling Profiles:
Capturing the complete trace can take time, especially for long-running operations. Polling profiles allow engineers to define how long and how often Tracetest should wait for the trace to be complete before proceeding with assertions. This ensures tests don't fail prematurely due to incomplete data.
Polling Profile UI vs. CLI
Beyond the Basics:
Tracetest also offers functionalities for managing test environments, user permissions, and integrations with other tools. These include:
Test Suites:
These allow engineers to chain multiple tests together. Each test within a suite can be configured independently, and data can be shared between tests, enabling the creation of more elaborate testing workflows. This can be particularly useful for scenarios requiring setup steps or utilizing data extracted from previous tests.
Test Suite Execution
Run Groups (Commercial Feature):
While not available in the free version, Tracetest offers Run Groups as a commercial feature. This functionality allows parallel execution of multiple tests within a group, potentially leading to faster test execution times, especially for large test sets.
Run Groups Status
As explored earlier, Playwright excels at automating browser interactions. However, for true end-to-end testing that examines interactions across microservices, Tracetest steps in. The Tracetest integration for Playwright allows us to seamlessly capture a complete distributed trace from our OpenTelemetry-instrumented application, encompassing both frontend and backend systems. We can embed Tracetest within our Playwright tests, enabling assertions against the entire trace data, leading to comprehensive end-to-end tests.
In order to showcase the practical integration of Tracetest with Playwright for trace-based testing, we'll be using the Pokeshop Demo App repository as a reference. This example provides a well-structured implementation and walks us through the steps of setting up, running, and understanding Playwright tests with Tracetest assertions.
git clone https://github.com/kubeshop/pokeshop.git
cd pokeshop
After cloning the GitHub repository, we'll need to configure our environment variables. To do this, we copy the .env.template file to a new file named .env. Additionally, to enable communication with Tracetest, we'll need to sign up for a free account on app.tracetest.io. Once we've created an account, we can obtain the TRACETEST_API_TOKEN and TRACETEST_AGENT_API_KEY from the Settings page within the Tracetest application. These tokens will be used to authenticate our tests with Tracetest.
While logged into the Tracetest app, we need to make sure the environment we'll use for this example is set to use the OpenTelemetry Tracing Backend. This matches how the Pokeshop Demo app is configured. We can find this setting under Settings > Tracing Backend and choose OpenTelemetry before saving.
To run the Pokeshop Demo application, we'll need Docker and Docker Compose installed on our machine. The easiest way to achieve this is by installing Docker Desktop, which will automatically install the required components for Docker functionality. Once Docker Desktop is installed, we can proceed with running the demo application using the following command:
docker compose -f docker-compose.yml -f docker-compose.e2e.yml up -d
This command instructs Docker Compose to use both the docker-compose.yml and docker-compose.e2e.yml configuration files to bring up all the necessary services for the demo application in detached mode.
Two ways to confirm that the Pokeshop Demo application is running successfully would be:
Pokeshop Demo App
The next step involves installing all the required dependencies for running the tests:
npm i
Before executing the tests located in playwright/home.spec.ts, let's delve into this file to understand how Tracetest is embedded within the Playwright tests. This will provide insights into how the tests interact with Tracetest for trace-based assertions.
The first steps in the script involve integrating Tracetest with Playwright. It begins by importing the @tracetest/playwright package, which facilitates the interaction between the two tools. Next, the script retrieves the Tracetest API token from the environment variables. Finally, it creates a Tracetest instance using this token, enabling communication with the Tracetest service.
import { test, expect } from '@playwright/test';
import Tracetest, { Types } from '@tracetest/playwright';
const { TRACETEST_API_TOKEN = '', TRACETEST_SERVER_URL = 'https://app.tracetest.io' } = process.env;
let tracetest: Types.TracetestPlaywright | undefined = undefined;
The script utilizes the beforeAll hook to create the Tracetest instance. This hook ensures the instance is created only once before all test cases are executed, optimizing performance and resource usage.
test.beforeAll(async () => {
tracetest = await Tracetest({ apiToken: TRACETEST_API_TOKEN, serverUrl: TRACETEST_SERVER_URL, serverPath: '' });
await tracetest.setOptions({
'Playwright: imports a pokemon': {
definition,
},
});
});
During the beforeEach hook, the script captures the document to inject the traceparent to the meta tag.
test.beforeEach(async ({ page }, info) => {
await page.goto('/');
await tracetest?.capture(page, info);
});
The script includes an optional step defined in the after hook. This hook allows us to call the summary method on the Tracetest instance. This functionality is useful if we want Playwright execution to halt in case a Tracetest test fails. This provides a mechanism to immediately identify and address failing trace-based assertions within our tests.
test.afterAll(async ({}, testInfo) => {
testInfo.setTimeout(80000);
await tracetest?.summary();
});
Interestingly, the remaining sections of the test script focus on defining Playwright tests for the various functionalities of the Pokeshop Demo app (creating, importing, deleting Pokemon). This implies that integrating Tracetest with our own testing applications would follow a similar approach. In essence, the core Playwright test structure for interacting with our application's UI would remain unchanged. The key difference would be adding the initial steps to set up Tracetest and potentially modifying the assertions to utilize trace data captured by Tracetest.
To run the tests using the Playwright UI, we can run the following command from the root directory:
npm run pw:open
After running all tests through the Playwright UI, we should see trace IDs displayed in the console output. These IDs will be accompanied by links to the corresponding Tracetest results. By clicking on these links, we can access detailed information about the captured traces within the Tracetest application. This allows us to analyze the execution flow and identify any issues within our application's backend interactions.
Playwright UI Test Results
The Tracetest library uses the test name for the trace-based tests. That way we can identify them more easily and it also fills some of the metadata directly from the Playwright execution.
Tracetest Results
With Tracetest integration in place, we can now create assertions based on the captured trace data. This data encompasses the entire request flow, from user interactions on the browser (click events, fetch requests) to backend activities (HTTP requests, database queries). This includes asynchronous processes as well, as demonstrated in the import Pokemon test.
The screenshot showcases two example assertions on the right side, targeting specific aspects of the captured trace data, such as HTTP requests and database spans. These assertions allow us to verify the expected behavior across our application's frontend and backend components.
The "Add Test Spec" button within the Tracetest UI facilitates the creation of new assertions. This involves selecting the relevant spans from the captured trace data. These spans represent specific events or actions within the trace, such as HTTP requests or database interactions. Once we've selected the spans, we can define assertions to verify their properties or behavior. Finally, we assign a descriptive name to our test spec to clearly communicate its purpose.
Adding New Test Specs
Traditional testing struggles with complex applications. Trace-based testing offers a solution, analyzing the entire request lifecycle across microservices for a more comprehensive view. This blog explored its benefits and Tracetest, a tool that streamlines the process for QA engineers.
Benefits:
Tracetest Features:
The Pokeshop Demo App showcased Tracetest integration with Playwright, capturing traces and defining assertions.
Embrace trace-based testing for a deeper understanding of your application and build more robust software. Tools like Tracetest empower QA engineers to write comprehensive tests and efficiently identify issues.
Additional resources: