From 9d55d1b489891589055302f2a30bb55318823be8 Mon Sep 17 00:00:00 2001 From: Augusto Silva Date: Tue, 27 Oct 2026 10:18:23 +0100 Subject: [PATCH 1/6] docs: add Netlify to the demo repository's README --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 370d5d2..f56cd7d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ 👉 **[Follow the Playwright E2E tutorial: From Cowboy to Confidence →](https://endform.dev/docs/tutorial/playwright-e2e-from-cowboy-to-confidence)** [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fendformdev%2Fplaywright-tutorial&project-name=endform-playwright-example&repository-name=endform-playwright-example&products=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22endform%22%2C%22productSlug%22%3A%22endform%22%2C%22protocol%22%3A%22other%22%7D%5D) +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/endformdev/playwright-tutorial) ## About the tutorial @@ -33,7 +34,13 @@ You can either: ## Coming from "Deploy with Vercel"? - Don't forget to connect your project with the Endform vercel integration -- You will need the `copy-me-github-workflows` copied to `.github/workflows` to get going with running your tests in github actions +- You will need to rename `rename-me-to-.github-for-workflows` to `.github` to get going with running your tests in GitHub Actions + +## Coming from "Deploy to Netlify"? + +- Install the Endform extension on your Netlify team, connect it to your Endform organization, then click **Configure project** on the new project +- The extension runs the tests against every Deploy Preview, so you don't need the GitHub Actions workflows +- To also see the results as a check on your pull requests, [install the Endform GitHub App](https://endform.dev/app/docs-link?link=settings-integrations) on the repository ## About the SaaS @@ -127,9 +134,15 @@ When you're ready to deploy your SaaS application to production, follow these st 2. Connect your repository to [Vercel](https://vercel.com/) and deploy it. 3. Follow the Vercel deployment process, which will guide you through setting up your project. +### Deploy to Netlify + +1. Push your code to a GitHub repository. +2. Import the repository on [Netlify](https://app.netlify.com/start). Netlify detects Next.js and pnpm, so the default build settings work. +3. Install the Endform extension to run the tests against each Deploy Preview. + ### Add environment variables -In your Vercel project settings (or during deployment), add the necessary environment variables: +In your Vercel or Netlify project settings (or during deployment), add the necessary environment variables: 1. `BASE_URL`: Set this to your production domain. 2. `DATABASE_URL`: Leave unset to use the shared Durable Object proxy, set it to a local SQLite path, set it to your own proxy URL, or use a Turso connection string (`libsql://your-database.turso.io`) From c4eae269bfe0392856accf2502e1822d42f4ce90 Mon Sep 17 00:00:00 2001 From: Augusto Silva Date: Tue, 27 Oct 2026 11:05:43 +0100 Subject: [PATCH 2/6] feat: get past Netlify's password page in the demo tests --- README.md | 1 + setup-utils.ts | 55 +++++++++++++++++++++++++++++++++++++++ tests/check-setup.spec.ts | 2 +- tests/setup.spec.ts | 2 +- tests/teardown.spec.ts | 2 +- tests/test.ts | 6 +++++ 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f56cd7d..d1d5b64 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ You can either: - Install the Endform extension on your Netlify team, connect it to your Endform organization, then click **Configure project** on the new project - The extension runs the tests against every Deploy Preview, so you don't need the GitHub Actions workflows +- If your Deploy Previews are password protected, add the password as a secret environment variable named `E2E_NETLIFY_PREVIEW_PASSWORD`, scoped to Deploy Previews. The tests use it to get past Netlify's password page (see [`setup-utils.ts`](setup-utils.ts) and [`tests/test.ts`](tests/test.ts)). To do the same in your own tests, follow the [guide in the Endform docs](https://endform.dev/docs/guides/netlify-integration/password-protected-deploy-previews). - To also see the results as a check on your pull requests, [install the Endform GitHub App](https://endform.dev/app/docs-link?link=settings-integrations) on the repository ## About the SaaS diff --git a/setup-utils.ts b/setup-utils.ts index 9edb223..a382b3c 100644 --- a/setup-utils.ts +++ b/setup-utils.ts @@ -1,4 +1,5 @@ import fs from "node:fs"; +import type { BrowserContext } from "@playwright/test"; export interface ApiUser { email: string; @@ -17,6 +18,7 @@ export async function createUser(baseURL: string): Promise { if (process.env.VERCEL_BYPASS_TOKEN) headers.set("x-vercel-protection-bypass", process.env.VERCEL_BYPASS_TOKEN); + await setNetlifyPasswordCookie(headers, baseURL); const response = await fetch(`${baseURL}/api/internal/user`, { method: "POST", @@ -50,6 +52,7 @@ export async function deleteUser(baseURL: string, email: string) { if (process.env.VERCEL_BYPASS_TOKEN) headers.set("x-vercel-protection-bypass", process.env.VERCEL_BYPASS_TOKEN); + await setNetlifyPasswordCookie(headers, baseURL); const response = await fetch(`${baseURL}/api/internal/user`, { method: "DELETE", @@ -90,6 +93,53 @@ export async function deleteUserFromFile( } } +let netlifyPasswordCookie: Promise<{ name: string; value: string } | null>; + +// Netlify answers every request to a password-protected deploy with its +// password page until the request carries the cookie that page sets. Posting +// the password once gets that cookie, which works for the whole deploy. +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, + }, + ]); +} + export function getCookieForSession(session: string, baseURL: string) { return { name: "session", @@ -121,3 +171,8 @@ function writeSessionFile(session: string, baseURL: string, filePath: string) { }), ); } + +async function setNetlifyPasswordCookie(headers: Headers, baseURL: string) { + const cookie = await getNetlifyPasswordCookie(baseURL); + if (cookie) headers.set("cookie", `${cookie.name}=${cookie.value}`); +} diff --git a/tests/check-setup.spec.ts b/tests/check-setup.spec.ts index 70a2ba7..894fad8 100644 --- a/tests/check-setup.spec.ts +++ b/tests/check-setup.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "./test"; test("has title", async ({ page }) => { await page.goto("/"); diff --git a/tests/setup.spec.ts b/tests/setup.spec.ts index 46e705b..24097f0 100644 --- a/tests/setup.spec.ts +++ b/tests/setup.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "./test"; const authFile = ".auth/shared-ui-user.json"; diff --git a/tests/teardown.spec.ts b/tests/teardown.spec.ts index 85390aa..50713cd 100644 --- a/tests/teardown.spec.ts +++ b/tests/teardown.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "./test"; test("delete current user account", async ({ page }) => { const testPassword = "testpassword123"; diff --git a/tests/test.ts b/tests/test.ts index 8b3a37e..388d912 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -1,5 +1,6 @@ import { test as base } from "@playwright/test"; import { test as playwrightOpentelemetryTest } from "playwright-opentelemetry/fixture"; +import { addNetlifyPasswordCookie } from "../setup-utils"; import { installFaultsForTest } from "./support/faults"; const playwrightOtelEnabled = Boolean( @@ -10,6 +11,11 @@ const playwrightOtelEnabled = Boolean( const testBase = playwrightOtelEnabled ? playwrightOpentelemetryTest : base; export const test = testBase.extend({ + context: async ({ context, baseURL }, use) => { + if (baseURL) await addNetlifyPasswordCookie(context, baseURL); + + await use(context); + }, page: async ({ page }, use, testInfo) => { await installFaultsForTest(page, testInfo); From 80b96e16750a7b4c71ddb4cd2c6bfed638736adf Mon Sep 17 00:00:00 2001 From: Augusto Silva Date: Tue, 27 Oct 2026 11:13:12 +0100 Subject: [PATCH 3/6] docs: exempt the preview password from Netlify's secrets scan --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1d5b64..4039ac8 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ You can either: - Install the Endform extension on your Netlify team, connect it to your Endform organization, then click **Configure project** on the new project - The extension runs the tests against every Deploy Preview, so you don't need the GitHub Actions workflows -- If your Deploy Previews are password protected, add the password as a secret environment variable named `E2E_NETLIFY_PREVIEW_PASSWORD`, scoped to Deploy Previews. The tests use it to get past Netlify's password page (see [`setup-utils.ts`](setup-utils.ts) and [`tests/test.ts`](tests/test.ts)). To do the same in your own tests, follow the [guide in the Endform docs](https://endform.dev/docs/guides/netlify-integration/password-protected-deploy-previews). +- If your Deploy Previews are password protected, add the password as a secret environment variable named `E2E_NETLIFY_PREVIEW_PASSWORD`, scoped to Deploy Previews, and add it to the project's `SECRETS_SCAN_OMIT_KEYS` so Netlify's secrets scan doesn't fail the build. The tests use it to get past Netlify's password page (see [`setup-utils.ts`](setup-utils.ts) and [`tests/test.ts`](tests/test.ts)). To do the same in your own tests, follow the [guide in the Endform docs](https://endform.dev/docs/guides/netlify-integration/password-protected-deploy-previews). - To also see the results as a check on your pull requests, [install the Endform GitHub App](https://endform.dev/app/docs-link?link=settings-integrations) on the repository ## About the SaaS From 32c252c8c440498cfd6356dd318002595b03f83c Mon Sep 17 00:00:00 2001 From: Augusto Silva Date: Tue, 27 Oct 2026 11:22:25 +0100 Subject: [PATCH 4/6] fix: block Netlify's Deploy Preview drawer in the demo tests --- tests/test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test.ts b/tests/test.ts index 388d912..0ce1f8e 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -13,6 +13,8 @@ const testBase = playwrightOtelEnabled ? playwrightOpentelemetryTest : base; export const test = testBase.extend({ context: async ({ context, baseURL }, use) => { if (baseURL) await addNetlifyPasswordCookie(context, baseURL); + // Netlify's Deploy Preview drawer can open over the page and catch clicks. + await context.route("**/.netlify/scripts/cdp*", (route) => route.abort()); await use(context); }, From b8ad045f82d4f958ac493b9fc3fdabed7514e844 Mon Sep 17 00:00:00 2001 From: Augusto Silva Date: Tue, 27 Oct 2026 11:33:03 +0100 Subject: [PATCH 5/6] fix: allow a cold first sign-up in the demo setup test --- tests/setup.spec.ts | 3 ++- tests/test.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/setup.spec.ts b/tests/setup.spec.ts index 24097f0..5f9b4f3 100644 --- a/tests/setup.spec.ts +++ b/tests/setup.spec.ts @@ -20,7 +20,8 @@ test("user signup and login flow", async ({ page }) => { }); await test.step("verify successful signup redirects to dashboard", async () => { - await expect(page).toHaveURL("/dashboard"); + // The first sign-up can hit a cold server right after a deploy. + await expect(page).toHaveURL("/dashboard", { timeout: 15_000 }); await expect( page.getByRole("heading", { name: "Team Settings" }), ).toBeVisible(); diff --git a/tests/test.ts b/tests/test.ts index 0ce1f8e..388d912 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -13,8 +13,6 @@ const testBase = playwrightOtelEnabled ? playwrightOpentelemetryTest : base; export const test = testBase.extend({ context: async ({ context, baseURL }, use) => { if (baseURL) await addNetlifyPasswordCookie(context, baseURL); - // Netlify's Deploy Preview drawer can open over the page and catch clicks. - await context.route("**/.netlify/scripts/cdp*", (route) => route.abort()); await use(context); }, From 5da8c8ca6b0d858fb75b31174e17689d71d7af7e Mon Sep 17 00:00:00 2001 From: Augusto Silva Date: Tue, 27 Oct 2026 15:45:45 +0100 Subject: [PATCH 6/6] docs: link the Endform Netlify extension in the README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4039ac8..acdafb6 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ You can either: ## Coming from "Deploy to Netlify"? -- Install the Endform extension on your Netlify team, connect it to your Endform organization, then click **Configure project** on the new project +- [Install the Endform extension](https://app.netlify.com/extensions/5exms8e7-endform) on your Netlify team, connect it to your Endform organization, then click **Configure project** on the new project - The extension runs the tests against every Deploy Preview, so you don't need the GitHub Actions workflows - If your Deploy Previews are password protected, add the password as a secret environment variable named `E2E_NETLIFY_PREVIEW_PASSWORD`, scoped to Deploy Previews, and add it to the project's `SECRETS_SCAN_OMIT_KEYS` so Netlify's secrets scan doesn't fail the build. The tests use it to get past Netlify's password page (see [`setup-utils.ts`](setup-utils.ts) and [`tests/test.ts`](tests/test.ts)). To do the same in your own tests, follow the [guide in the Endform docs](https://endform.dev/docs/guides/netlify-integration/password-protected-deploy-previews). - To also see the results as a check on your pull requests, [install the Endform GitHub App](https://endform.dev/app/docs-link?link=settings-integrations) on the repository @@ -139,7 +139,7 @@ When you're ready to deploy your SaaS application to production, follow these st 1. Push your code to a GitHub repository. 2. Import the repository on [Netlify](https://app.netlify.com/start). Netlify detects Next.js and pnpm, so the default build settings work. -3. Install the Endform extension to run the tests against each Deploy Preview. +3. [Install the Endform extension](https://app.netlify.com/extensions/5exms8e7-endform) to run the tests against each Deploy Preview. ### Add environment variables