How to Handle OAuth2.0 Authentication in API Testing with Playwright

To test authenticated APIs in our dev/stage/prod environment using OAuth 2.0, we can create a new request context with the token from the OAuth 2.0 authentication. In the Playwright test file, we can add the logic for creating the new request context in the test.beforeAll hook.

My test file code is


import { test, expect, APIRequestContext } from '@playwright/test';
import { getRequestContext } from '../utils/playwright-context';
import postEnquiryData from '../mocks/request_body/post-enquiry';
import patchEnquiryData from '../mocks/request_body/patch-enquiry';

let apiContext: APIRequestContext;
const id = '01FRQW9R32AT50CS5BWE3JCWZR';

test.describe('Enquiries E2E tests', () => {
  test.beforeAll(async () => {
    apiContext = await getRequestContext({
      apiEndpoint: process.env.API_ENDPOINT as string,
      tokenEndpoint: process.env.OIDC_TOKEN_URL as string,
      tokenClientId: process.env.OIDC_CLIENT_ID as string,
      tokenClientSecret: process.env.OIDC_CLIENT_SECRET as string,
    });
  });

  test('Create an Enquiry', async () => {
    const enquiryCreateResponse = await apiContext.post(`/`, {
      data: postEnquiryData,
    });
    expect(enquiryCreateResponse.ok()).toBeTruthy();
    const enquiryCreateInfo = await enquiryCreateResponse.json();
    expect(enquiryCreateInfo.data).toHaveProperty('id');
  });

  test('Patch an Enquiry', async () => {
    const patchResponse = await apiContext.patch(`/XXXX`, {
      data: patchEnquiryData,
    });
    expect(patchResponse.ok()).toBeTruthy();
  });

  test.afterAll(async () => {
    await apiContext.dispose();
  });
});

and my playwright-context.ts content is

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

type TestConfig = {
  apiEndpoint: string;
  tokenEndpoint: string;
  tokenClientId: string;
  tokenClientSecret: string;
};

function getBearerToken(clientId: string, secret: string) {
  const btoa = (str: string) => Buffer.from(str).toString('base64');
  const credentialsBase64 = btoa(`${clientId}:${secret}`);
  return credentialsBase64;
}

export async function getRequestContext(testConfig: TestConfig) {
  const authContext = await request.newContext({
    baseURL: testConfig.tokenEndpoint,
    extraHTTPHeaders: {
      Authorization: `Basic ${getBearerToken(testConfig.tokenClientId, testConfig.tokenClientSecret)}`,
    },
  });

  const signinResponse = await authContext.post('', {
    form: {
      grant_type: 'client_credentials',
    },
  });
  expect(signinResponse.ok()).toBeTruthy();
  const authInfo = await signinResponse.json();
  const token = authInfo.access_token;

  const apiContext = await request.newContext({
    baseURL: testConfig.apiEndpoint,
    extraHTTPHeaders: {
      Authorization: `Bearer ${token}`,
    },
  });
  return apiContext;
}