Skip to content

Private and password-protected Deploy Previews

If your Netlify project protects its Deploy Previews, every request from your tests gets Netlify’s login or password page instead of your app. In the deploy log, this usually shows as failing tests and 401 Unauthorized responses.

Check your project’s protection under Project configuration > General > Visitor access > Project visibility. Previews can be public, private, or password protected, separately from production.

Netlify makes new projects private by default. A private project asks every visitor to sign in with Netlify, and Netlify has no way for automated tests to get past it.

To test private Deploy Previews, change the preview visibility:

  • On any plan: make previews public.
  • On Pro plans: protect previews with a password instead, and let your tests enter it as described below.

Production can stay private either way.

Password protection is only available on Netlify Pro plans.

Netlify’s password page is a form that posts a password field. When the password is right, Netlify sets a cookie that gives access to the whole deploy. Your tests can post the password once and add that cookie to every browser context and API request.

  1. In your Netlify project’s environment variables, add the password as a secret variable named E2E_NETLIFY_PREVIEW_PASSWORD, scoped to Deploy Previews.

    Variables that start with E2E_ are sent to Endform’s remote runners automatically.

    Also add E2E_NETLIFY_PREVIEW_PASSWORD to the project’s SECRETS_SCAN_OMIT_KEYS variable. Build tools like Next.js cache the build environment, so Netlify’s secrets scan would otherwise find the password in the build output and fail the build.

  2. Add a helper that posts the password and reads the cookie Netlify sets:

    netlify-password.ts
    import type { BrowserContext } from "@playwright/test";
    let netlifyPasswordCookie: Promise<{ name: string; value: string } | null>;
    export function getNetlifyPasswordCookie(baseURL: string) {
    const password = process.env.E2E_NETLIFY_PREVIEW_PASSWORD;
    if (!password) return Promise.resolve(null);
    netlifyPasswordCookie ??= fetch(baseURL, {
    method: "POST",
    body: new URLSearchParams({ password }),
    redirect: "manual",
    }).then((response) => {
    const [cookie] = response.headers.getSetCookie();
    if (!cookie) {
    throw new Error(
    `Netlify rejected E2E_NETLIFY_PREVIEW_PASSWORD (${response.status})`,
    );
    }
    const [name, value] = cookie.split(";")[0]!.split("=");
    return { name: name!, value: value! };
    });
    return netlifyPasswordCookie;
    }
    export async function addNetlifyPasswordCookie(
    context: BrowserContext,
    baseURL: string,
    ) {
    const cookie = await getNetlifyPasswordCookie(baseURL);
    if (!cookie) return;
    await context.addCookies([
    {
    ...cookie,
    domain: new URL(baseURL).hostname,
    path: "/",
    httpOnly: true,
    secure: true,
    },
    ]);
    }

    Without the variable, for example against production or a local server, nothing changes.

  3. Override the context fixture in the test your tests import:

    tests/test.ts
    import { test as base } from "@playwright/test";
    import { addNetlifyPasswordCookie } from "../netlify-password";
    export const test = base.extend({
    context: async ({ context, baseURL }, use) => {
    if (baseURL) await addNetlifyPasswordCookie(context, baseURL);
    await use(context);
    },
    });
    export { expect } from "@playwright/test";

    Then import test and expect from this file instead of @playwright/test, including in setup projects that log in through the UI.

  4. Requests made outside a browser context, such as fetch calls in a global setup that create test users, need the cookie as a header:

    const cookie = await getNetlifyPasswordCookie(baseURL);
    if (cookie) headers.set("cookie", `${cookie.name}=${cookie.value}`);

The demo repository uses this setup. See setup-utils.ts and tests/test.ts.