Hello my fellows AQA Engineers! This is continuation of my previous article: Revolutionize GraphQL testing with auto generated type safe client.
Today I will guide you through creating a completely type-safe client framework for testing GraphQL APIs with Playwright Test.
Our goal is to set up a project for code generation and create a fixture with a GraphQL API client that will completely eliminate the need to define queries and mutations in your code.
Let's use this "Pokemon" API playground as our system under test: https://graphql-pokeapi.graphcdn.app
The link above will navigate you to the built-in GraphQL explorer that inspects the API, shows you all available queries, and allows you to write and send GraphQL requests.
We are going to automate all this boring and repetitive work!
As prerequisites, you should have a Node/TypeScript project with initialised Playwright Test.
Lets start:
Step 1. Installation:
npm install -D playwright-graphql
Step 2. Generate GraphQL type safe client via CLI:
playwright-graphql --url https://graphql-pokeapi.graphcdn.app
This command will generate the following files:
π Project Root
βββ π schema.gql (schema file extracted from url)
βββ π codegen.ts (config file allows client types customisation)
βββ π gql (directory for autogenerated files)
βββ π autogenerated-operations (GraphQL queries/mutations)
β βββ π mutations.gql
β βββ π queries.gql
βββ π graphql.ts (TypeScript types and getClient method)
Step 3. Add path to your tsconfig:
To simplify your imports and improve project readability, configure your tsconfig.json by adding custom path aliases.
This makes it easier to import your generated GraphQL client across your project:
Add "@gql": ["gql/graphql"]
for easy import.
{
"compilerOptions": {
"target": "ESNext",
"module": "Node16",
"baseUrl": "./",
"paths": {
"@fixtures/*": ["fixtures/*"],
"@gql": ["gql/graphql"]
}
}
}
This setup allows you to import your client like this:
import { getClient, GqlAPI } from '@gql';
Pay attention that the path @fixtures/* allows you to resolve each file in the directory as a module:
import { test, expect } from '@fixtures/gql';
Step 4. Create gql fixture file:
fixtures/gql.ts
import { test as baseTest, expect, request, APIRequestContext } from '@playwright/test';
// getClient function is factory for GraphQL client.
// GqlAPI represents type with all queries and mutations
import { getClient, GqlAPI } from '@gql';
export { expect };
type WorkerFixtures = {
apiContext: APIRequestContext;
gql: GqlAPI;
};
export const test = baseTest.extend<{}, WorkerFixtures>({
apiContext: [
async ({}, use) => {
const apiContext = await request.newContext({
baseURL: 'https://graphql-pokeapi.graphcdn.app'
});
await use(apiContext);
}, { scope: 'worker' }
],
gql: [
async ({ apiContext }, use) => {
// gql endpoint is empty just because our pokemon SUT
// does not use any graphql endpoint like 'api/graphql'
await use(getClient(apiContext, { gqlEndpoint: '' }));
}, { auto: false, scope: 'worker' }
]
});
This fixture ensures that your tests have a consistent and type-safe GraphQL client available, and it leverages
Playwrightβs API request context for efficient testing.
Step 5. You are ready jump into writing tests!
tests/pokemon.test:
import { test, expect } from "@fixtures/gql";
test('playwright-graphql test', async ({ gql }) => {
const res = await gql.pokemons({
limit: 2,
offset: 1,
});
expect(res.pokemons?.results).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'venusaur' })
])
);
})
Now dear reader you do not have excuse to build strings in GrapgQL tests with Playwright.
Negative test:
By design GraphQL protocol always returns 200 code and in case error occurs it will be send in payload. By default Playwright-Graphql client throws an error if property data
in payload is undefined or null because when error received instead of expected data from DB client will not be able to map this on any type, you can turn off this behaviour:
test('playwright-graphql negative test', async ({ gql }) => {
const res = await gql.region({
// @ts-ignore
region: undefined
}, { failOnEmptyData: false });
expect(res).toHaveProperty('errors[0].message', 'Variable "$region" of required type "String!" was not provided.');
})
Comment // @te-ignore
turns off TS protection and allows you to not send required field, used just for demonstration.
Option failOnEmptyData: false
just turn off response verification.
Coverage reporter
Since our focus is on input parameters (variables) that can be pass to queries and mutations we need a tool that will help us track what we've already covered in our tests and what still needs to be tested.
You already generated the GraphQL Schema for the Pokemon API playground, you can regenerate the existing autogenerated TypeScript file with coverage logging:
playwright-graphql --coverage
Now the CLI interface will not call the remote server a second time, instead it will regenerate only the graphql.ts
file. This version of autogenerated code will include coverage logging.
Add the coverage reporter to your playwright.config.ts
file:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: 'tests',
reporter: [
['list'],
['html', { open: 'never' }],
['playwright-graphql/coverage-reporter', {
graphqlFilePath: './gql/graphql.ts',
minCoveragePerOperation: 20, // percentage, default 100
logUncoveredOperations: true,
saveGqlCoverageLog: false,
coverageFilePath: './gql-coverage.log',
saveHtmlSummary: true
}]
],
});
Pay attention that graphqlFilePath
should point on autogenerated file graphql.ts
from step 2
Run your tests:
npm test
This will produce the following console log output:
npx playwright test
Running 1 test using 1 worker
β 1 tests/pokemon.test.ts:3:5 βΊ playwright-graphql test (333ms)
1 passed (1.1s)
To open last HTML report run:
npx playwright show-report
============================================================
GQL Operations coverage in executed tests: 3.57%
Total operations: 28
Covered operations: 1
Total arguments coverage: 3.57%
================= Uncovered operations =====================
abilities 0%
ability 0%
berries 0%
berry 0%
eggGroup 0%
eggGroups 0%
encounterMethod 0%
encounterMethods 0%
evolutionChain 0%
evolutionChains 0%
evolutionTrigger 0%
evolutionTriggers 0%
gender 0%
genders 0%
growthRate 0%
growthRates 0%
location 0%
locations 0%
move 0%
moves 0%
nature 0%
natures 0%
pokemon 0%
region 0%
regions 0%
species 0%
types 0%
============================================================
This shows you a high level summary of coverage on queries and mutations level.
But that's not all.
Check your root directory and you will find a gql-coverage.html
file there. Open it in your browser.
This HTML report page provides detailed information about covered input parameters for each query and mutation. It's capable of calculating enum parameter types and nested levels of parameters.
By implementing this type-safe GraphQL testing framework with Playwright, you've eliminated the error-prone process of manually writing GraphQL queries and gained valuable insights into your test coverage. The combination of auto-generated clients and comprehensive coverage reporting not only makes your tests more robust and maintainable but also helps you identify untested operations and parameters at a glance. As you expand your test suite, you'll appreciate how this approach scales effortlessly while providing immediate feedback on your testing progress. This framework transforms GraphQL API testing from a tedious task into a streamlined, efficient process that both new team members and experienced engineers will find intuitive and powerful.
Top comments (0)