2

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. Super simple.

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.

Any help with this would be gratefully received.

PeterByte
  • 3,534
  • 2
  • 20
  • 16

3 Answers3

5

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.

PeterByte
  • 3,534
  • 2
  • 20
  • 16
3

Probably the simplest way would be to add basic auth credentials to baseURL:

baseURL: 'https://username:password@example.com'

(special characters have to be url encoded i.e. '@' becomes '%40').

tadek
  • 31
  • 3
1

If using chrome, begin the test by navigating to the url with included username & password to login. Then navigate again to the url without credentials to load the page as expected in a logged in state.

async function login(page: Page) {
  const url = new URL('http://myuser:password@localhost:8080')
  await page.goto(url.href)   // full url
  await page.goto(url.origin) // http://localhost:8080
}

As per Chrome 59 and Basic Authentication with Selenium/Fluentlenium

Nick
  • 6,366
  • 5
  • 43
  • 62