level 05 / graphql-and-soap

GraphQL & SOAP Testing

Test GraphQL queries and SOAP endpoints using Playwright's request fixture.

practice site: Countries GraphQL API

GraphQL Over HTTP

GraphQL is HTTP POST with a JSON body containing query and optional variables. Playwright’s request.post() handles it identically to REST.

import { test, expect } from '@playwright/test';

test('query countries list', async ({ request }) => {
  const res = await request.post('https://countries.trevorblades.com/', {
    data: {
      query: `query { countries { code name } }`,
    },
  });
  expect(res.status()).toBe(200);
  const { data } = await res.json();
  expect(data.countries.length).toBeGreaterThan(100);
});

Variables and Fragments

test('query single country with variables', async ({ request }) => {
  const res = await request.post('https://countries.trevorblades.com/', {
    data: {
      query: `
        query GetCountry($code: ID!) {
          country(code: $code) {
            name
            capital
            currency
          }
        }
      `,
      variables: { code: 'US' },
    },
  });
  const { data } = await res.json();
  expect(data.country.name).toBe('United States');
  expect(data.country.capital).toBe('Washington D.C.');
});

GraphQL Error Handling

GraphQL always returns HTTP 200 — errors appear in the errors array in the response body. Check both:

test('invalid query returns graphql error', async ({ request }) => {
  const res = await request.post('https://countries.trevorblades.com/', {
    data: { query: `query { nonExistentField }` },
  });
  expect(res.status()).toBe(200);       // GraphQL: always 200
  const body = await res.json();
  expect(body.errors).toBeDefined();    // Error in body
  expect(body.data).toBeNull();
});

Mutations

// Hypothetical write API
test('create mutation', async ({ request }) => {
  const res = await request.post('/graphql', {
    headers: { Authorization: `Bearer ${token}` },
    data: {
      query: `
        mutation CreatePost($title: String!) {
          createPost(title: $title) { id title }
        }
      `,
      variables: { title: 'Hello World' },
    },
  });
  const { data } = await res.json();
  expect(data.createPost.id).toBeDefined();
});

SOAP Testing

SOAP uses HTTP POST with an XML body. Playwright’s request fixture handles it with a Content-Type: text/xml header:

test('soap calculator add', async ({ request }) => {
  const soapBody = `
    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Body>
        <Add xmlns="http://tempuri.org/">
          <intA>5</intA>
          <intB>3</intB>
        </Add>
      </soap:Body>
    </soap:Envelope>
  `.trim();

  const res = await request.post('http://www.dneonline.com/calculator.asmx', {
    headers: {
      'Content-Type': 'text/xml; charset=utf-8',
      SOAPAction: '"http://tempuri.org/Add"',
    },
    data: soapBody,
  });
  expect(res.status()).toBe(200);
  const text = await res.text();
  expect(text).toContain('<AddResult>8</AddResult>');
});

GraphQL vs REST vs SOAP

GraphQLRESTSOAP
TransportHTTP POSTHTTP verbsHTTP POST
FormatJSONJSON/XMLXML
HTTP status on errorAlways 2004xx/5xx200 or 500
SchemaSDL (schema.graphql)OpenAPIWSDL
Assertion strategyCheck body.errorsCheck statusParse XML