Skip to content

Commit f5a1020

Browse files
docs: add Netlify to the demo repository's README (#9)
* docs: add Netlify to the demo repository's README * feat: get past Netlify's password page in the demo tests * docs: exempt the preview password from Netlify's secrets scan * fix: block Netlify's Deploy Preview drawer in the demo tests * fix: allow a cold first sign-up in the demo setup test * docs: link the Endform Netlify extension in the README
1 parent d6cc989 commit f5a1020

6 files changed

Lines changed: 82 additions & 6 deletions

File tree

‎README.md‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
👉 **[Follow the Playwright E2E tutorial: From Cowboy to Confidence →](https://endform.dev/docs/tutorial/playwright-e2e-from-cowboy-to-confidence)**
66

77
[![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)
8+
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/endformdev/playwright-tutorial)
89

910
## About the tutorial
1011

@@ -33,7 +34,14 @@ You can either:
3334
## Coming from "Deploy with Vercel"?
3435

3536
- Don't forget to connect your project with the Endform vercel integration
36-
- You will need the `copy-me-github-workflows` copied to `.github/workflows` to get going with running your tests in github actions
37+
- You will need to rename `rename-me-to-.github-for-workflows` to `.github` to get going with running your tests in GitHub Actions
38+
39+
## Coming from "Deploy to Netlify"?
40+
41+
- [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
42+
- The extension runs the tests against every Deploy Preview, so you don't need the GitHub Actions workflows
43+
- 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).
44+
- 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
3745

3846
## About the SaaS
3947

@@ -127,9 +135,15 @@ When you're ready to deploy your SaaS application to production, follow these st
127135
2. Connect your repository to [Vercel](https://vercel.com/) and deploy it.
128136
3. Follow the Vercel deployment process, which will guide you through setting up your project.
129137

138+
### Deploy to Netlify
139+
140+
1. Push your code to a GitHub repository.
141+
2. Import the repository on [Netlify](https://app.netlify.com/start). Netlify detects Next.js and pnpm, so the default build settings work.
142+
3. [Install the Endform extension](https://app.netlify.com/extensions/5exms8e7-endform) to run the tests against each Deploy Preview.
143+
130144
### Add environment variables
131145

132-
In your Vercel project settings (or during deployment), add the necessary environment variables:
146+
In your Vercel or Netlify project settings (or during deployment), add the necessary environment variables:
133147

134148
1. `BASE_URL`: Set this to your production domain.
135149
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`)

‎setup-utils.ts‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from "node:fs";
2+
import type { BrowserContext } from "@playwright/test";
23

34
export interface ApiUser {
45
email: string;
@@ -17,6 +18,7 @@ export async function createUser(baseURL: string): Promise<ApiUser> {
1718

1819
if (process.env.VERCEL_BYPASS_TOKEN)
1920
headers.set("x-vercel-protection-bypass", process.env.VERCEL_BYPASS_TOKEN);
21+
await setNetlifyPasswordCookie(headers, baseURL);
2022

2123
const response = await fetch(`${baseURL}/api/internal/user`, {
2224
method: "POST",
@@ -50,6 +52,7 @@ export async function deleteUser(baseURL: string, email: string) {
5052

5153
if (process.env.VERCEL_BYPASS_TOKEN)
5254
headers.set("x-vercel-protection-bypass", process.env.VERCEL_BYPASS_TOKEN);
55+
await setNetlifyPasswordCookie(headers, baseURL);
5356

5457
const response = await fetch(`${baseURL}/api/internal/user`, {
5558
method: "DELETE",
@@ -90,6 +93,53 @@ export async function deleteUserFromFile(
9093
}
9194
}
9295

96+
let netlifyPasswordCookie: Promise<{ name: string; value: string } | null>;
97+
98+
// Netlify answers every request to a password-protected deploy with its
99+
// password page until the request carries the cookie that page sets. Posting
100+
// the password once gets that cookie, which works for the whole deploy.
101+
export function getNetlifyPasswordCookie(baseURL: string) {
102+
const password = process.env.E2E_NETLIFY_PREVIEW_PASSWORD;
103+
if (!password) return Promise.resolve(null);
104+
105+
netlifyPasswordCookie ??= fetch(baseURL, {
106+
method: "POST",
107+
body: new URLSearchParams({ password }),
108+
redirect: "manual",
109+
}).then((response) => {
110+
const [cookie] = response.headers.getSetCookie();
111+
if (!cookie) {
112+
throw new Error(
113+
`Netlify rejected E2E_NETLIFY_PREVIEW_PASSWORD (${response.status})`,
114+
);
115+
}
116+
117+
const [name, value] = cookie.split(";")[0]!.split("=");
118+
119+
return { name: name!, value: value! };
120+
});
121+
122+
return netlifyPasswordCookie;
123+
}
124+
125+
export async function addNetlifyPasswordCookie(
126+
context: BrowserContext,
127+
baseURL: string,
128+
) {
129+
const cookie = await getNetlifyPasswordCookie(baseURL);
130+
if (!cookie) return;
131+
132+
await context.addCookies([
133+
{
134+
...cookie,
135+
domain: new URL(baseURL).hostname,
136+
path: "/",
137+
httpOnly: true,
138+
secure: true,
139+
},
140+
]);
141+
}
142+
93143
export function getCookieForSession(session: string, baseURL: string) {
94144
return {
95145
name: "session",
@@ -121,3 +171,8 @@ function writeSessionFile(session: string, baseURL: string, filePath: string) {
121171
}),
122172
);
123173
}
174+
175+
async function setNetlifyPasswordCookie(headers: Headers, baseURL: string) {
176+
const cookie = await getNetlifyPasswordCookie(baseURL);
177+
if (cookie) headers.set("cookie", `${cookie.name}=${cookie.value}`);
178+
}

‎tests/check-setup.spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, test } from "@playwright/test";
1+
import { expect, test } from "./test";
22

33
test("has title", async ({ page }) => {
44
await page.goto("/");

‎tests/setup.spec.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, test } from "@playwright/test";
1+
import { expect, test } from "./test";
22

33
const authFile = ".auth/shared-ui-user.json";
44

@@ -20,7 +20,8 @@ test("user signup and login flow", async ({ page }) => {
2020
});
2121

2222
await test.step("verify successful signup redirects to dashboard", async () => {
23-
await expect(page).toHaveURL("/dashboard");
23+
// The first sign-up can hit a cold server right after a deploy.
24+
await expect(page).toHaveURL("/dashboard", { timeout: 15_000 });
2425
await expect(
2526
page.getByRole("heading", { name: "Team Settings" }),
2627
).toBeVisible();

‎tests/teardown.spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, test } from "@playwright/test";
1+
import { expect, test } from "./test";
22

33
test("delete current user account", async ({ page }) => {
44
const testPassword = "testpassword123";

‎tests/test.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { test as base } from "@playwright/test";
22
import { test as playwrightOpentelemetryTest } from "playwright-opentelemetry/fixture";
3+
import { addNetlifyPasswordCookie } from "../setup-utils";
34
import { installFaultsForTest } from "./support/faults";
45

56
const playwrightOtelEnabled = Boolean(
@@ -10,6 +11,11 @@ const playwrightOtelEnabled = Boolean(
1011
const testBase = playwrightOtelEnabled ? playwrightOpentelemetryTest : base;
1112

1213
export const test = testBase.extend({
14+
context: async ({ context, baseURL }, use) => {
15+
if (baseURL) await addNetlifyPasswordCookie(context, baseURL);
16+
17+
await use(context);
18+
},
1319
page: async ({ page }, use, testInfo) => {
1420
await installFaultsForTest(page, testInfo);
1521

0 commit comments

Comments
 (0)