<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksandr Solomin</title>
    <description>The latest articles on DEV Community by Oleksandr Solomin (@danteukraine).</description>
    <link>https://dev.to/danteukraine</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2848486%2F65a8fdfe-7b62-4b58-a862-52218a6d7d35.jpg</url>
      <title>DEV Community: Oleksandr Solomin</title>
      <link>https://dev.to/danteukraine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danteukraine"/>
    <language>en</language>
    <item>
      <title>Setup type safe Playwright-Graphql client.</title>
      <dc:creator>Oleksandr Solomin</dc:creator>
      <pubDate>Mon, 03 Mar 2025 15:53:14 +0000</pubDate>
      <link>https://dev.to/danteukraine/setup-type-safe-playwright-graphql-client-jem</link>
      <guid>https://dev.to/danteukraine/setup-type-safe-playwright-graphql-client-jem</guid>
      <description>&lt;p&gt;Hello my fellows AQA Engineers! This is continuation of my previous article: &lt;a href="https://dev.to/danteukraine/playwright-graphql-revolutionize-graphql-testing-with-auto-generated-type-safe-client-4i1l"&gt;Revolutionize GraphQL testing with auto generated type safe client&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today I will guide you through creating a completely type-safe client framework for testing GraphQL APIs with Playwright Test.&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
Let's use this "Pokemon" API playground as our system under test: &lt;code&gt;https://graphql-pokeapi.graphcdn.app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;We are going to automate all this boring and repetitive work!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As prerequisites, you should have a Node/TypeScript project with initialised Playwright Test.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Lets start: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;. Installation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -D playwright-graphql&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;. Generate GraphQL type safe client via CLI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;playwright-graphql --url https://graphql-pokeapi.graphcdn.app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command will generate the following files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📁 Project Root
├── 📄 schema.gql (generated schema file)
└── 📁 gql (default directory for generated files)
    ├──📁 schema
    │  └── 📁 autogenerated-operations (GraphQL queries/mutations)
    │      ├── 📄 mutations.gql
    │      └── 📄 queries.gql
    └── 📄 graphql.ts (generated TypeScript types and client)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;. Add path to your tsconfig:&lt;/p&gt;

&lt;p&gt;To simplify your imports and improve project readability, configure your tsconfig.json by adding custom path aliases.&lt;br&gt;
This makes it easier to import your generated GraphQL client across your project:&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;"@gql": ["gql/graphql"]&lt;/code&gt; for easy import.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@fixtures/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"fixtures/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@gql"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"gql/graphql"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup allows you to import your client like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GqlAPI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@gql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay attention that the path @fixtures/* allows you to resolve each file in the directory as a module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/gql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;. Create gql fixture file:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;fixtures/gql.ts&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;baseTest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// getClient function is factory for GraphQL client.&lt;/span&gt;
&lt;span class="c1"&gt;// GqlAPI represents type with all queries and mutations&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GqlAPI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@gql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;WorkerFixtures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;apiContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GqlAPI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseTest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;WorkerFixtures&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;apiContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://graphql-pokeapi.graphcdn.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;apiContext&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// gql endpoint is empty just because our pokemon SUT&lt;/span&gt;
            &lt;span class="c1"&gt;// does not use any graphql endpoint like 'api/graphql' &lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;gqlEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fixture ensures that your tests have a consistent and type-safe GraphQL client available, and it leverages&lt;br&gt;
Playwright’s API request context for efficient testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;. You are ready jump into writing tests!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;tests/pokemon.test&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@fixtures/gql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright-graphql test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pokemons&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pokemons&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayContaining&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectContaining&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;venusaur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now dear reader you do not have excuse to build strings in GrapgQL tests with Playwright.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Negative test:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;data&lt;/code&gt; 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright-graphql negative test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;region&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;// @ts-ignore &lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;failOnEmptyData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;errors[0].message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Variable "$region" of required type "String!" was not provided.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comment &lt;code&gt;// @te-ignore&lt;/code&gt; turns off TS protection and allows you to not send required field, used just for demonstration. &lt;/p&gt;

&lt;p&gt;Option &lt;code&gt;failOnEmptyData: false&lt;/code&gt; just turn off response verification. &lt;/p&gt;

&lt;h2&gt;
  
  
  Coverage reporter
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;You already generated the GraphQL Schema for the Pokemon API playground, you can regenerate the existing autogenerated TypeScript file with coverage logging:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;playwright-graphql --coverage&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the CLI interface will not call the remote server a second time, instead it will regenerate only the &lt;code&gt;graphql.ts&lt;/code&gt; file. This version of autogenerated code will include coverage logging.&lt;/p&gt;

&lt;p&gt;Add the coverage reporter to your &lt;code&gt;playwright.config.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright-graphql/coverage-reporter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;graphqlFilePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./gql/graphql.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;minCoveragePerOperation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// percentage, default 100&lt;/span&gt;
            &lt;span class="na"&gt;logUncoveredOperations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;saveGqlCoverageLog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;coverageFilePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./gql-coverage.log&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;saveHtmlSummary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay attention that &lt;code&gt;graphqlFilePath&lt;/code&gt; should point on autogenerated file &lt;code&gt;graphql.ts&lt;/code&gt; from &lt;strong&gt;step 2&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Run your tests:&lt;br&gt;
&lt;code&gt;npm test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will produce the following console log output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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%
============================================================

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows you a high level summary of coverage on queries and mutations level.&lt;/p&gt;

&lt;p&gt;But that's not all.&lt;/p&gt;

&lt;p&gt;Check your root directory and you will find a &lt;code&gt;gql-coverage.html&lt;/code&gt; file there. Open it in your browser.&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnau8ct5usdthgxovs8ng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnau8ct5usdthgxovs8ng.png" alt="Image description" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>playwright</category>
      <category>testautomation</category>
      <category>apitesting</category>
    </item>
    <item>
      <title>Playwright-graphql: Revolutionize GraphQL Testing with Auto-Generated Type-Safe Client</title>
      <dc:creator>Oleksandr Solomin</dc:creator>
      <pubDate>Sat, 22 Feb 2025 12:18:48 +0000</pubDate>
      <link>https://dev.to/danteukraine/playwright-graphql-revolutionize-graphql-testing-with-auto-generated-type-safe-client-4i1l</link>
      <guid>https://dev.to/danteukraine/playwright-graphql-revolutionize-graphql-testing-with-auto-generated-type-safe-client-4i1l</guid>
      <description>&lt;p&gt;Playwright-graphql changes how we test GraphQL APIs. It creates client SDKs from your GraphQL schema, removing the need to write queries by hand. This tool combines Playwright's testing power with GraphQL's flexibility, making tests easier to write and maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before diving into GraphQL client-based automated tests, let me share a brief story about why I decided to build this integration.
&lt;/h2&gt;

&lt;p&gt;When I first encountered a GraphQL API at work, I did what any engineer would do when faced with something unfamiliar, I googled it. I searched for "How to automate GraphQL with Playwright." The results, were disappointing. Every solution from seemingly relevant sources relied on using queries and mutations as raw strings in the codebase or, at best, employed rudimentary string builders.&lt;/p&gt;

&lt;p&gt;This approach felt wrong. It was clunky, error-prone, and missed a critical point about how GraphQL APIs work under the hood. Let’s explore why this is a common mistake and how Playwright-GraphQL solves it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem with String-Based GraphQL Operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To understand why using raw strings for GraphQL operations is problematic, we first need to understand how GraphQL APIs work behind the scenes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How GraphQL Works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GraphQL servers convert your queries into SQL queries, and mutations into CREATE, UPDATE, or DELETE operations in the database. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
        location(locationId:1428){
          id
          name
          created
        }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will be transformed into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT id, name, created FROM locations WHERE id = 1428;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The power of GraphQL lies in its flexibility. It allows developers to request only the data they need. However, this flexibility comes with a responsibility: ensuring that input parameters (like locationId) are tested thoroughly. Why? Because input parameters drive the backend logic (e.g., SQL WHERE conditions), while query fields (like id, name, created) just define which properties are selected in the response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Real Goal of GraphQL Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The primary goal of GraphQL testing should be to validate how input parameters affect backend behaviour. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are filters applied correctly?&lt;/li&gt;
&lt;li&gt;Are limits (take) and offsets (skip) handled properly?&lt;/li&gt;
&lt;li&gt;Does data in response sorted as expected? &lt;/li&gt;
&lt;li&gt;Does the API return 
the expected results for edge cases?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Focusing on query fields instead of input parameters is a mistake because query fields don’t affect backend logic they only control what’s added in the response. To effectively test a GraphQL API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Maximize SQL Query Coverage:&lt;/strong&gt; Ensure your tests trigger as many variations of SQL WHERE conditions as possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Overloading Queries:&lt;/strong&gt; Keep parameters like take (used for limiting results) reasonable to avoid extracting excessive data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How Playwright-GraphQL Solves This Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Playwright-GraphQL eliminates the need for raw strings in your codebase by generating type-safe operations directly from your GraphQL schema. This approach ensures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Focus on Input Parameters:&lt;/strong&gt; Tests validate how input parameters affect backend logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readable Tests:&lt;/strong&gt; Tests focus on &lt;strong&gt;what is being tested&lt;/strong&gt;, not &lt;strong&gt;how it is being tested&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety:&lt;/strong&gt; Auto-generated types prevent invalid queries or mutations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s an example without: &lt;a href="https://www.npmjs.com/package/playwright-graphql" rel="noopener noreferrer"&gt;playwright-graphql&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7k1i1zhkkzyqgbae6hm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7k1i1zhkkzyqgbae6hm7.png" alt="Image description" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how the same test looks with &lt;a href="https://www.npmjs.com/package/playwright-graphql" rel="noopener noreferrer"&gt;playwright-graphql&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70bn0208397px8hbffr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70bn0208397px8hbffr4.png" alt="Image description" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Playwright-GraphQL is Better:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No Raw Strings:&lt;/strong&gt; You no longer need to write raw GraphQL queries or manually manage variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety:&lt;/strong&gt; The generated client ensures that only valid operations and parameters can be used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner Assertions:&lt;/strong&gt; The response is already parsed and structured, so you can directly assert on the returned data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By using Playwright-GraphQL, you avoid common pitfalls like forgetting to parse the JSON payload or misnaming fields in your queries. It simplifies your tests and allows you to focus on validating business logic instead of worrying about GraphQL protocol details.&lt;/p&gt;

&lt;p&gt;🔑 &lt;strong&gt;Main Feature: Schema-Driven Automation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike other GraphQL testing methods, this tool &lt;strong&gt;makes complete client SDKs&lt;/strong&gt; directly from your GraphQL schema:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get Schema get-graphql-schema pulls your GraphQL endpoint definition&lt;/li&gt;
&lt;li&gt;Make Operations gql-generator creates all possible queries/mutations&lt;/li&gt;
&lt;li&gt;Add TypeScript GraphQL Codegen makes fully-typed client methods with autocomplete&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;NPM Package: &lt;a href="https://www.npmjs.com/package/playwright-graphql" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/playwright-graphql&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Template repo: &lt;a href="https://github.com/DanteUkraine/playwright-graphql-example" rel="noopener noreferrer"&gt;https://github.com/DanteUkraine/playwright-graphql-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Playwright-graphql makes GraphQL testing easier. It creates your API client automatically, so you can focus on writing good tests instead of managing API details. This saves time and helps catch errors early.&lt;/p&gt;

&lt;p&gt;Continuation: &lt;a href="https://dev.to/danteukraine/setup-type-safe-playwright-graphql-client-jem"&gt;Setup type safe Playwright-GraphQL client&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>playwright</category>
      <category>testautomation</category>
      <category>apiautomation</category>
    </item>
  </channel>
</rss>
