Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add boilerplate for playwright e2e testing #2519

Merged
merged 16 commits into from
Jan 25, 2025
Merged
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ src/customLayers.json
# testing
/coverage
chimp.js
error.png

# production
/build
Expand All @@ -21,6 +22,7 @@ chimp.js
.DS_Store
.env.local
.env.*.local
.env.playwright
/public/env.json

npm-debug.log*
Expand All @@ -37,3 +39,10 @@ yarn-error.log*

#vscode
/.vscode
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

playwright/.auth/
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
"lint": "eslint src/",
"format": "biome format --write",
"check": "biome ci && eslint src/",
"explore": "source-map-explorer --only-mapped --no-border-checks 'dist/**/*.js'"
"explore": "source-map-explorer --only-mapped --no-border-checks 'dist/**/*.js'",
"test:e2e:start": "NODE_ENV=development yarn run build && npx serve dist",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug"
},
"browserslist": [
">0.2%",
Expand Down Expand Up @@ -120,8 +124,10 @@
"@biomejs/biome": "1.9.4",
"@eslint/js": "^9.9.0",
"@openstreetmap/id-tagging-schema": "^3.0.0",
"@playwright/test": "^1.49.1",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^12.1.2",
"@types/node": "^22.10.5",
"@vitejs/plugin-react-swc": "^3.7.1",
"@vitest/coverage-v8": "^2.1.2",
"dotenv": "^16.4.5",
Expand Down
87 changes: 87 additions & 0 deletions playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import path from "path";
import { defineConfig, devices } from "@playwright/test";
import dotenv from "dotenv";
import { fileURLToPath } from "url";

// Replicate __dirname functionality in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
dotenv.config({ path: path.resolve(__dirname, ".env.local") });

// Simplified environment variable handling
const requiredEnvVars = {
REACT_APP_USERNAME: process.env.REACT_APP_USERNAME,
REACT_APP_PASSWORD: process.env.REACT_APP_PASSWORD,
REACT_APP_URL: process.env.REACT_APP_URL,
};

// Validate required environment variables
Object.entries(requiredEnvVars).forEach(([key, value]) => {
if (!value) {
throw new Error(
`Required environment variable ${key} is missing. Please add it to .env.local`
);
}
});

/**
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: "./playwright/tests",
headless: true, // Run in headless mode for faster execution
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: "html",
globalSetup: "./playwright/global-setup.js",

use: {
baseURL: process.env.REACT_APP_URL || "http://localhost:3000",
storageState: "./playwright/.auth/state.json",
trace: "on-first-retry",
navigationTimeout: 30000,
actionTimeout: 15000,
},

projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
},
},
{
name: "webkit",
use: {
...devices["Desktop Safari"],
},
},
{
name: "edge",
use: {
...devices["Desktop Edge"],
},
},
],

webServer: {
command: "yarn run test:e2e:start",
url: process.env.REACT_APP_URL || "http://localhost:3000",
reuseExistingServer: !process.env.CI,
timeout: 30000,
env: requiredEnvVars,
},
});

Check warning on line 87 in playwright.config.js

View check run for this annotation

Codecov / codecov/patch

playwright.config.js#L1-L87

Added lines #L1 - L87 were not covered by tests
39 changes: 39 additions & 0 deletions playwright/global-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { chromium } from "@playwright/test";

async function globalSetup(config) {
const storageState = "./playwright/.auth/state.json";
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

try {
// Navigate and sign in
await page.goto(process.env.REACT_APP_URL || "http://localhost:3000");
await page.locator("a").filter({ hasText: "Sign in" }).click();

console.log(process.env.REACT_APP_USERNAME);
console.log(process.env.REACT_APP_PASSWORD);
// Handle OSM login
await page.locator("#username").fill(process.env.REACT_APP_USERNAME);
await page.locator("#password").fill(process.env.REACT_APP_PASSWORD);
await page.locator('input[type="submit"][value="Log in"]').click();

// Handle OAuth if needed
try {
const authorizeButton = await page.waitForSelector(
'input[type="submit"][value="Authorize"]',
{ timeout: 5000 }
);
if (authorizeButton) {
await authorizeButton.click();
}
} catch (e) {} // Ignore if no authorization needed

await context.storageState({ path: storageState });
} finally {
await context.close();
await browser.close();
}
}

export default globalSetup;
23 changes: 23 additions & 0 deletions playwright/tests/loggedInNavigation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { test, expect } from "@playwright/test";

test.describe("Logged in navigation", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("networkidle");
await page
.getByRole("banner")
.locator("a")
.filter({ hasText: "Sign in" })
.click();
});

test("should navigate to Find Challenges", async ({ page }) => {
await page
.getByRole("navigation")
.getByRole("link", { name: "Find Challenges" })
.click();
await expect(
page.getByRole("heading", { name: "Challenges" }).locator("span")
).toBeVisible();
});
});
57 changes: 57 additions & 0 deletions playwright/tests/loggedOutNavigation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { test, expect } from "@playwright/test";

// Create a new test fixture that doesn't use the stored auth state
test.describe("Logged out navigation", () => {
// Use a new context for these tests without the stored auth state
test.use({ storageState: { cookies: [], origins: [] } });

test.beforeEach(async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("networkidle");
});

test("should load find challenges page", async ({ page }) => {
await page
.getByRole("navigation")
.getByRole("link", { name: "Find Challenges" })
.click();
await expect(
page.getByRole("heading", { name: "Challenges" }).locator("span")
).toBeVisible();
await expect(
page.locator("a").filter({ hasText: "Sign in" })
).toBeVisible();
});

test("should load leaderboard page", async ({ page }) => {
await page
.getByRole("navigation")
.getByRole("link", { name: "Leaderboard" })
.click();
await page.waitForLoadState("networkidle");
});

test("should load learn page", async ({ page }) => {
await page
.getByRole("navigation")
.getByRole("link", { name: "Learn" })
.click();
await page.waitForLoadState("networkidle");
});

test("should load blog page", async ({ page }) => {
await page
.getByRole("navigation")
.getByRole("link", { name: "Blog" })
.click();
await page.waitForLoadState("networkidle");
});

test("should load donate page", async ({ page }) => {
await page
.getByRole("navigation")
.getByRole("link", { name: "Donate" })
.click();
await page.waitForLoadState("networkidle");
});
});
24 changes: 24 additions & 0 deletions playwright/tests/login.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test } from "@playwright/test";

test.describe("Logged in navigation", () => {
test.use({ storageState: { cookies: [], origins: [] } });

test("should login and redirect to maproulette", async ({ page }) => {
await page.goto("/");
await page
.getByRole("banner")
.locator("a")
.filter({ hasText: "Sign in" })
.click();
await page
.getByLabel("Email Address or Username")
.fill(process.env.REACT_APP_USERNAME || "");
await page
.getByLabel("Password")
.fill(process.env.REACT_APP_PASSWORD || "");
await page.getByRole("button", { name: "Log in" }).click();
await page.waitForLoadState("networkidle");
await page.waitForURL("**/dashboard");
page.getByRole("link", { name: "My Points" });
});
});
14 changes: 14 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,18 @@
__GIT_SHA__: JSON.stringify(execSync('git rev-parse HEAD').toString()),
__GIT_TAG__: JSON.stringify(execSync('git describe --tags --exact-match 2>/dev/null || true').toString()),
},
test: {
exclude: ['**/playwright/**', '**/node_modules/**', '**/dist/**'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'**/playwright/**',
'**/node_modules/**',
'**/dist/**',
'**/*.test.*',
'**/*.spec.*'
]
}
},

Check warning on line 34 in vite.config.js

View check run for this annotation

Codecov / codecov/patch

vite.config.js#L21-L34

Added lines #L21 - L34 were not covered by tests
});
38 changes: 38 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,13 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==

"@playwright/test@^1.49.1":
version "1.49.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.1.tgz#55fa360658b3187bfb6371e2f8a64f50ef80c827"
integrity sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==
dependencies:
playwright "1.49.1"

"@popperjs/core@^2.8.4":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
Expand Down Expand Up @@ -1506,6 +1513,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b"
integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==

"@types/node@^22.10.5":
version "22.10.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.5.tgz#95af89a3fb74a2bb41ef9927f206e6472026e48b"
integrity sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==
dependencies:
undici-types "~6.20.0"

"@types/normalize-package-data@^2.4.0":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
Expand Down Expand Up @@ -3839,6 +3853,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==

fsevents@2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==

fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
Expand Down Expand Up @@ -5922,6 +5941,20 @@ piwik-react-router@^0.12.1:
url-join "^1.1.0"
warning "^3.0.0"

playwright-core@1.49.1:
version "1.49.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.49.1.tgz#32c62f046e950f586ff9e35ed490a424f2248015"
integrity sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==

playwright@1.49.1:
version "1.49.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.49.1.tgz#830266dbca3008022afa7b4783565db9944ded7c"
integrity sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==
dependencies:
playwright-core "1.49.1"
optionalDependencies:
fsevents "2.3.2"

polylabel@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/polylabel/-/polylabel-1.1.0.tgz#9483e64fc7a12a49f43e07e7a06752214ed2a8e7"
Expand Down Expand Up @@ -8000,6 +8033,11 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==

undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==

unherit@^1.0.4:
version "1.1.3"
resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22"
Expand Down
Loading