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

[1508] Add Playwright for e2e testing #1535

Closed
wants to merge 13 commits into from
15 changes: 13 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
# dependencies
/node_modules
/.pnp
.pnp.js
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
functions/.yarn/*

# testing
/coverage
Expand Down Expand Up @@ -72,4 +79,8 @@ build-storybook.log

/token

cert.txt
cert.txt
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,25 @@ git pull upstream main
- `yarn dev:up:detach`: Run the application, and keep it running once you stop this command.
- `yarn dev:down`: Stop the application.
- `yarn dev:update`: Update the application images. Run this whenever dependencies in `package.json` change.
- `yarn test:integration [--watch] [-t testNamePattern] [my/feature.test.ts]`: Run integration tests in `components/` and `tests/integration/`. These tests run against the full local application -- start it with `yarn up`. You can use `--watch` to rerun your tests as you change them and filter by test name and file.

Install the [Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) and [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) browser extensions if you're developing frontend

## Contributing Backend Features to Dev/Prod:

- If you are developing backend features involving only Next.js API routes and need to deploy them to the Dev site, download [Google application credentials for the dev project](https://console.firebase.google.com/u/0/project/digital-testimony-dev/settings/serviceaccounts/adminsdk) (you will need to be added as an editor of the project). Then, run `export GOOGLE_APPLICATION_CREDENTIALS=path-to-credentials.json` before running `yarn dev`. This is necessary to authenticate the Firebase Admin SDK. The same would apply to production.

## Testing

MAPLE uses Jest for unit and integration testing, and Playwright for e2e testing.

To start running tests, use one of the following commands:

- `yarn test:integration [--watch] [-t testNamePattern] [my/feature.test.ts]`: Run integration tests in `components/` and `tests/integration/`. These tests run against the full local application -- start it with `yarn up`. You can use `--watch` to rerun your tests as you change them and filter by test name and file.
- `yarn test:e2e`: Run e2e tests in `tests/e2e` with the Playwright UI
- `yarn test:e2e:headless`: Run e2e tests in `tests/e2e` headless (no UI)

For an introduction on how to write e2e tests with Playwright, go to the [Playwright docs](https://playwright.dev/docs/writing-tests). An example of an e2e test can be found in `tests/e2e/homepage.spec.ts`.

## Code Formatting and Linting

We use Prettier and ESLint to check files for consistent formatting and catch common programming errors. When you send out a PR, these run as part of the [`Repo Checks`](https://github.com/codeforboston/maple/actions/workflows/repo-checks.yml) workflow.
Expand Down
40 changes: 40 additions & 0 deletions jest.resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Upgrading to Jest 28+ introduced an error ESM imports: https://github.com/microsoft/accessibility-insights-web/pull/5421#issuecomment-1109168149
module.exports = (path, options) => {
// Call the defaultResolver, so we leverage its cache, error handling, etc.
return options.defaultResolver(path, {
...options,
// Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
packageFilter: pkg => {
const pkgNamesToTarget = new Set([
"@google-cloud/storage",
"@firebase/analytics",
"@firebase/app",
"@firebase/auth",
"@firebase/storage",
"@firebase/functions",
"@firebase/auth-compat",
"@firebase/app-compat",
"@firebase/compat",
"@firebase/firestore",
"@firebase/firestore-compat",
"@firebase/functions-compat",
"@firebase/util",
"firebase",
"firebase/admin",
"firebase/auth",
"firebase/firestore",
"firebase/functions",
"firebase/storage",
"uuid",
"nanoid"
])

if (pkgNamesToTarget.has(pkg.name)) {
delete pkg["exports"]
delete pkg["module"]
}

return pkg
}
})
}
18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"lint:fix": "next lint --fix",
"serve": "firebase emulators:start --only hosting",
"start": "next start",
"test:e2e": "playwright test --ui",
"test:e2e:headless": "playwright test",
"test:integration": "jest -c tests/jest.integration.config.ts -w 1",
"test:integration-ci": "node scripts/test-integration-ci.js",
"test:system": "jest -c tests/jest.system.config.ts",
Expand Down Expand Up @@ -72,6 +74,7 @@
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@playwright/test": "^1.43.1",
"@popperjs/core": "^2.11.8",
"@react-aria/ssr": "^3.2.0",
"@react-aria/utils": "^3.13.1",
Expand All @@ -89,7 +92,7 @@
"firebase": "9.6.10",
"fuse.js": "6.5.3",
"handlebars": "^4.7.8",
"i18next": "^23.5.1",
"i18next": "^23.11.2",
"i18next-http-backend": "^2.2.2",
"instantsearch.css": "^7.4.5",
"instantsearch.js": "^4.43.0",
Expand All @@ -100,7 +103,7 @@
"marked": "^4.2.5",
"nanoid": "^3.3.1",
"next": "^14.0.4",
"next-i18next": "^13.1.5",
"next-i18next": "^15.3.0",
"next-redux-wrapper": "^8.1.0",
"papaparse": "^5.3.0",
"react": "^18.2.0",
Expand All @@ -111,7 +114,7 @@
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.33.1",
"react-i18next": "^13.2.2",
"react-i18next": "^14.1.0",
"react-inlinesvg": "^3.0.1",
"react-is": "^18.2.0",
"react-markdown": "^8.0.4",
Expand Down Expand Up @@ -143,7 +146,7 @@
"@tsconfig/node16": "^1.0.2",
"@types/diff": "^5.0.2",
"@types/dompurify": "^2.3.3",
"@types/jest": "^27.4.0",
"@types/jest": "^29.5.12",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.178",
"@types/luxon": "^2.0.9",
Expand All @@ -168,8 +171,8 @@
"firebase-tools": "^11.16.0",
"ini": "^1.3.5",
"inquirer": "^6.5.1",
"jest": "^27.5.1",
"jest-environment-jsdom": "^27.5.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-summary-reporter": "^0.0.2",
"js-yaml": "^4.1.0",
"lorem-ipsum": "^2.0.4",
Expand All @@ -187,5 +190,6 @@
},
"resolutions": {
"jackspeak": "2.1.1"
}
},
"packageManager": "yarn@1.22.22"
}
77 changes: 77 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { defineConfig, devices } from "@playwright/test"

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests/e2e",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry"
},

/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] }
},

{
name: "firefox",
use: { ...devices["Desktop Firefox"] }
},

{
name: "webkit",
use: { ...devices["Desktop Safari"] }
}

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: "yarn dev",
url: "http://localhost:3000/",
reuseExistingServer: !process.env.CI
}
})
13 changes: 13 additions & 0 deletions tests/e2e/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { test, expect, type Page } from "@playwright/test"

test.beforeEach(async ({ page }) => {
await page.goto("http://localhost:3000")
})

test.describe("Maple Homepage", () => {
test("should display logo and text", async ({ page }) => {
const logo = page.getByAltText("logo").first()
expect(logo).toBeVisible()
expect(page.getByText("Let your voice be heard!")).toBeVisible()
})
})
53 changes: 31 additions & 22 deletions tests/integrationEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,41 @@ import { Config } from "@jest/types"
import BrowserEnvironment from "jest-environment-jsdom"
import timers from "timers"

interface ConfigTypes {
globalConfig: Config.GlobalConfig
projectConfig: Config.ProjectConfig
}
class IntegrationEnvironment extends BrowserEnvironment {
constructor(config: Config.ProjectConfig) {
constructor({ globalConfig, projectConfig }: ConfigTypes, context: any) {
super(
Object.assign({}, config, {
globals: Object.assign({}, config.globals, {
// https://github.com/firebase/firebase-js-sdk/issues/3096#issuecomment-637584185
Uint32Array: Uint32Array,
Uint8Array: Uint8Array,
ArrayBuffer: ArrayBuffer,
{
globalConfig,
projectConfig: {
...projectConfig,
globals: Object.assign({}, projectConfig.globals, {
// https://github.com/firebase/firebase-js-sdk/issues/3096#issuecomment-637584185
Uint32Array: Uint32Array,
Uint8Array: Uint8Array,
ArrayBuffer: ArrayBuffer,

// These are required to run the admin sdk in a jsdom environment
setImmediate: timers.setImmediate,
setTimeout: timers.setTimeout,
setInterval: timers.setInterval,
clearImmediate: timers.clearImmediate,
clearTimeout: timers.clearTimeout,
clearInterval: timers.clearInterval,
// These are required to run the admin sdk in a jsdom environment
setImmediate: timers.setImmediate,
setTimeout: timers.setTimeout,
setInterval: timers.setInterval,
clearImmediate: timers.clearImmediate,
clearTimeout: timers.clearTimeout,
clearInterval: timers.clearInterval,

/** jsdom's Blob implementation does not work with firebase/storage.
* firebase/storage *does* work with a fallback if Blob is not
* available, so removing the global is a hack to get storage tests
* working. We'll need a better solution when tests need to use Blobs.
* */
Blob: undefined
})
})
/** jsdom's Blob implementation does not work with firebase/storage.
* firebase/storage *does* work with a fallback if Blob is not
* available, so removing the global is a hack to get storage tests
* working. We'll need a better solution when tests need to use Blobs.
* */
Blob: undefined
})
}
},
context
)
}

Expand Down
4 changes: 3 additions & 1 deletion tests/jest.integration.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ const config: Config.InitialOptions = {
rootDir: "..",
testPathIgnorePatterns: [
"/node_modules/",
"<rootDir>/tests/e2e",
"<rootDir>/tests/system",
"<rootDir>/tests/seed",
"<rootDir>/functions"
],
setupFilesAfterEnv: ["<rootDir>/tests/setup.integration.ts"],
modulePaths: ["<rootDir>"],
reporters: ["default", ["jest-summary-reporter", { failuresOnly: false }]]
reporters: ["default", ["jest-summary-reporter", { failuresOnly: false }]],
resolver: "<rootDir>/jest.resolver.js"
}

// See https://nextjs.org/docs/advanced-features/compiler#jest
Expand Down