Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Playwright basic authentication for API test

Tags:

playwright

I am trying to use Playwright to carry out an API test. The simple case is that I am trying to get info on a user. To do this with curl, I can issue the command:

curl --user username:password https://example.com/api/user/id

This will respond with some JSON.

I have read through the Playwright docs, watched some YouTube videos and scoured various sources, but can't find out how to replicate this in Playwright.

My requests are consistently getting a response of "403 Forbidden".

In my playwright.config.ts file, I have added httpCredentials like so:

import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

const config: PlaywrightTestConfig = {
  [...]

  use: {
    headless: false,
    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
    actionTimeout: 0,
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: 'https://example.com',
    httpCredentials: {
      username: 'username',
      password: 'password'
    },

  [...]

Meanwhile, in my apiExperiment.spec.ts file:

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

test.describe('Test the API', () => {
    test('Get user info', async({request}) => {
        let userInfo = await request.post('/api/user/id');
    });
});

As I said previously, this just results in "403 Forbidden".

I have tried variations on this theme, like removing the httpCredentials from the config file, then changing the apiExperiment.spec.ts file to:

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

test.describe('Test the API', () => {
    test('Get user info', async({request}) => {
        let userInfo = await request.post('/api/user/id', {
            data: {
                username: 'username',
                password: 'password',
            }
        });
    });
});

and another variation:

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

test.describe('Test the API', () => {
    test('Get user info', async({request, browser}) => {
        const context = await browser.newContext({
            httpCredentials: {username: 'username', password: 'password'}
        });

        let userInfo = await context.request.post('/api/user/id');
    });
});

but to no avail.

like image 273
PeterByte Avatar asked Aug 27 '26 15:08

PeterByte


1 Answers

After some extensive digging around, I managed to resolve this. The issue is that apparently if you use httpCredentials, Playwright will make a request omitting the Authorization header. It then expects a 401 status code in the response and, if it gets that, will repeat the request with the credentials specified in the header.

This behaviour is a problem for me, as a request without credentials results in a response code of 403.

Before talking about the fix that I applied, I should add the caveat that it may not be suitable in all cases - I made changes in the config file as I wanted the Authorization header to be used throughout my tests. Also, I'm sure there are more secure/preferred ways of storing the username and password than hard-coding them into a config file, but my concern here was to get something working.

Bearing in mind those caveats, the way I got this working was to encode the username and password and manually define the Authorization header in playwright.config.ts:

import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

const httpCredentials = {
    username: 'username',
    password: 'password',
  };
  
const btoa = (str: string) => Buffer.from(str).toString('base64');
const credentialsBase64 = btoa(`${httpCredentials.username}:${httpCredentials.password}`);

const config: PlaywrightTestConfig = {
    // ...
    use: {
        // ...

        /* Base URL to use in actions like `await page.goto('/')`. */
        baseURL: 'https://example.com',
    
        extraHTTPHeaders: {
          'Authorization': `Basic ${credentialsBase64}`
        },
    // ...
};

Following this, the Authorization header is added to every call to the server and everything works as expected.

like image 85
PeterByte Avatar answered Aug 30 '26 13:08

PeterByte