Skip to content

Commit f5ba169

Browse files
add some example tests
1 parent e13a6ec commit f5ba169

File tree

18 files changed

+369
-60
lines changed

18 files changed

+369
-60
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ COURSE_BASE_URL=http://localhost:3000/courses
1313
FIELDS_HUGO_CONFIG_PATH=/path/to/ocw-hugo-projects/mit-fields/config.yaml
1414
FIELDS_CONTENT_PATH=/path/to/fields-site/
1515
VERBOSE=1
16+
WEBPACK_PORT=3001

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ An example environment file can be found at `.env.example`. To further explain
168168
| `FIELDS_CONTENT_PATH` | `fields` | `/path/to/ocw-content-rc/philosophy` | A path to a Hugo site that will be rendered when running `npm run start:fields` |
169169
| `VERBOSE` | N/A | `0` | Used in `build_all_courses.sh`, if set to `1` this will print verbose output from the course builds to the console |
170170
| `DOWNLOAD` | N/A | `1` | Used in `npm run start:course`, if set to `0` this will not download course data from S3 and instead source `OCW_TEST_COURSE` from the specified `OCW_TO_HUGO_OUTPUT_DIR`. If not specified, it will default to 1 and try to download data from S3. |
171+
| `WEBPACK_PORT` | N/A | `3001` | Port used by Webpack Dev Server |
171172
| `WEBPACK_ANALYZE` | N/A | `true` | Used in webpack build. If set to `true`, a dependency analysis of the bundle will be included in the build output. |
172173

173174
### Running ocw-www
@@ -235,6 +236,9 @@ To customize your `fields` site:
235236
- Start the site with `npm run start:fields`
236237
- The site should be available at http://localhost:3000/
237238

239+
### Writing Tests
240+
Most tests in OCW Hugo Themes should be written as e2e tests with Playwright. See [End to End Testing](./tests-e2e/README.md) for more.
241+
238242
### Miscellaneous commands
239243

240244
- `WEBPACK_ANALYZE=true yarn run build:webpack`: This builds the project for production and should open a an analysis of the bundle in your web browser.
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<div class="card">
22
<div class="card-body">
3-
<div class="course-detail-section">
3+
<section class="course-detail-section">
44
{{ partial "course_description.html" (dict "context" .) }}
5-
</div>
5+
</section>
66

7-
<div class="course-detail-section">
7+
<section class="course-detail-section">
88
{{ partial "course_info.html" (dict "context" . "inPanel" false) }}
9-
</div>
9+
</section>
1010

11-
<div class="course-detail-section">
11+
<section class="course-detail-section">
1212
{{ partial "learning_resource_types.html" (dict "context" . "inPanel" false) }}
13-
</div>
13+
</section>
1414
</div>
1515
</div>

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"build:webpack": "cross-env NODE_ENV=production webpack --config base-theme/assets/webpack/webpack.prod.ts",
2424
"webpack:stats": "NODE_ENV=production webpack --config base-theme/assets/webpack/webpack.prod.ts --profile --json > stats.json",
2525
"webpack:analyze": "webpack-bundle-analyzer stats.json dist",
26-
"fmt": "LOG_LEVEL= prettier-eslint --write --no-semi $PWD/'**/assets/**/*.js' $PWD/'**/assets/**/*.ts' $PWD/'**/assets/**/*.tsx' $PWD/'**/assets/**/*.scss'",
26+
"fmt": "LOG_LEVEL= prettier-eslint --write --no-semi $PWD/'**/assets/**/*.js' $PWD/'**/assets/**/*.ts' $PWD/'**/assets/**/*.tsx' $PWD/'**/assets/**/*.scss' $PWD/'tests-e2e/**/*.ts' ",
2727
"fmt:check": "LOG_LEVEL= prettier-eslint --list-different --no-semi $PWD/'**/assets/**/*.js' $PWD/'**/assets/**/*.ts' $PWD/'**/assets/**/*.tsx' $PWD/'**/assets/**/*.scss'",
2828
"scss_lint": "node ./node_modules/sass-lint/bin/sass-lint.js --verbose --no-exit ./assets",
2929
"test": "jest",
@@ -59,6 +59,7 @@
5959
"@types/webpack-bundle-analyzer": "^4.6.0",
6060
"@types/webpack-dev-server": "^4.7.2",
6161
"@typescript-eslint/eslint-plugin": "^5.40.1",
62+
"ansi-colors": "^4.1.3",
6263
"archiver": "^5.0.0",
6364
"assets-webpack-plugin": "^7.1.1",
6465
"babel-loader": "^8.2.5",
@@ -67,6 +68,7 @@
6768
"casual-browserify": "^1.5.19-2",
6869
"clean-webpack-plugin": "^3.0.0",
6970
"cli-progress": "^3.8.2",
71+
"cli-table3": "^0.6.3",
7072
"concurrently": "^6.3.0",
7173
"copy-webpack-plugin": "^11.0.0",
7274
"core-js": "^3.20.2",

package_scripts/build.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ set -euo pipefail
44

55
source package_scripts/common.sh
66

7-
export STATIC_API_BASE_URL=https://ocw.mit.edu/
8-
97
# We should have 2 arguments; the path to the content and the config
108
if [[ $# -lt 2 ]]; then
119
echo "Usage: build.sh /path/to/site /path/to/config.yaml"

playwright.config.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import type { PlaywrightTestConfig } from '@playwright/test'
1+
import { PlaywrightTestConfig } from '@playwright/test'
22
import { devices } from '@playwright/test'
33
import dotenv from 'dotenv'
44
import * as path from "path"
5+
import * as envalid from "envalid"
56

67
dotenv.config()
8+
const env = envalid.cleanEnv(process.env, {
9+
WEBPACK_PORT: envalid.port()
10+
})
711

812
/**
913
* See https://playwright.dev/docs/test-configuration.
@@ -34,7 +38,7 @@ const config: PlaywrightTestConfig = {
3438
],
3539
webServer: {
3640
command: "yarn start:webpack",
37-
url: "http://localhost:3001/static/css/main.css",
41+
url: `http://localhost:${env.WEBPACK_PORT}/static/css/main.css`,
3842
reuseExistingServer: !process.env.CI,
3943
},
4044
globalSetup: path.resolve(__dirname, './tests-e2e/global-setup.ts'),

test-sites/ocw-ci-test-course/content/pages/shortcode-demos.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ uid: 7e1d1926-96b4-4f0c-b4b6-2f34ab7562f6
77
---
88
### Resource Link
99

10-
Check no extra spaces xxx{{% resource_link "38167987-b1af-4544-86a1-909a7cdb4f18" "First Test Page" %}}xxx
10+
Check no extra spaces xxx{{% resource_link "38167987-b1af-4544-86a1-909a7cdb4f18" "Resource link to First Test Page" %}}xxx

test-sites/ocw-ci-test-course/data/course.json

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"term": "Fall",
33
"year": "2022",
44
"level": [
5-
"Graduate"
5+
"Graduate",
6+
"Undergraduate"
67
],
78
"topics": [
89
[
@@ -19,8 +20,11 @@
1920
"legacy_uid": "",
2021
"instructors": {
2122
"content": [
22-
"a0af4753-e784-4e5e-b23b-1a5911b15283",
23-
"144c1f31-44a3-23a9-7819-21a612f6574f"
23+
"a16d0fec-adce-ec6a-10b1-d8153a7427e6",
24+
"a32373bc-8c78-e37a-f79e-1f15686d1a9b",
25+
"144c1f31-44a3-23a9-7819-21a612f6574f",
26+
"0bb74f02-6e52-c6eb-1b90-fe7d782be397",
27+
"c8b71de9-118e-b585-3e09-8cbe2bcfc6f2"
2428
],
2529
"website": "ocw-www"
2630
},
@@ -29,9 +33,11 @@
2933
"website": "ocw-ci-test-course"
3034
},
3135
"course_title": "OCW CI Test Course",
32-
"course_description": "This is a test course for use in CI pipelines.",
36+
"course_description": "This is a test course for use in CI pipelines. This course can be found:\n\n- On Github at \u00a0[https://github.mit.edu/ocw-content-rc/ocw-ci-test-course](https://github.mit.edu/ocw-content-rc/ocw-ci-test-course)\n- On OCW Studio at [https://ocw-studio-rc.odl.mit.edu/sites/ocw-ci-test-course](https://ocw-studio-rc.odl.mit.edu/sites/ocw-ci-test-course)\n\nThe content of this course is additionally checked into the `ocw-hugo-themes` repository.\n\n\u00a0Recommended usage by OL Engineers:\n\n- Clone the github repository to your local.\n- When developing on `ocw-hugo-themes`, make edits in Studio as needed for the tests you want to write. Pull to local, and copy the files to `ocw-hugo-themes/test-sites/ocw-ci-test-course`.",
3337
"department_numbers": [
34-
"8"
38+
"8",
39+
"6",
40+
"18"
3541
],
3642
"extra_course_numbers": "456",
3743
"primary_course_number": "123",

tests-e2e/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# End-to-end Testing
2+
3+
This repository uses [Playwright](https://playwright.dev/) for end-to-end testing. Roughly, the Playwright tests build static assets with Webpack and Hugo then use headless browsers to manipulate the generated pages. We make assertions about the pages using Playwright's typescript library [`@playwright/test`](https://playwright.dev/docs/intro).
4+
5+
In OCW Hugo Themes, **most tests should be e2e**. See [Why playwright?](#why-playwright) for more.
6+
7+
## FAQ
8+
9+
### How to Run Playwright
10+
11+
To run the tests:
12+
1. `yarn start:webpack` in one shell, and
13+
2. `yarn test:e2e` in another. *Or `yarn test:e2e --debug` to step through tests.
14+
15+
Running `yarn test:e2e` by itself will also work. However, if you start a webpack server in a separate process first, Playwright will use that server instead rather than starting a new one, resulting in much less overhead if you need to re-run tests.
16+
17+
### Is there a `--watch` mode for re-running tests?
18+
19+
No. For VS Code users, a similar experience can be achieved with [Playwright for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). Individual tests can be run from your editor with the click of a button.
20+
21+
### How do I generate test content?
22+
The easiest way is probably through Studio. See [https://ocw-studio-rc.odl.mit.edu/sites/ocw-ci-test-course](https://ocw-studio-rc.odl.mit.edu/sites/ocw-ci-test-course/type/metadata/), for example.
23+
24+
### Why Playwright?
25+
Historically, we've used Jest and JSDom to test *some* aspects of OCW Hugo Themes. However, building any significant confidence in OCW Hugo Themes via Jest+JSDom has proven difficult for two reasons: (1) a large portion of the repo's logic is in Hugo templates, and (2) the output of Hugo is static HTML files, not JS modules. Neither of these issues is a challenge when testing Hugo's output via Playwright, and we get other benefits, too. (For example: ability to test features like infinite scroll that rely on layout, or the possibility to do visual testing.)

tests-e2e/global-setup.ts

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,80 @@
1-
import type { ExecOptions } from "node:child_process"
2-
import http from "node:http"
3-
1+
import { ExecOptions } from "node:child_process"
2+
import * as http from "node:http"
3+
import * as path from "node:path"
44
import dotenv from "dotenv"
55
import execShCb from "exec-sh"
66
import * as envalid from "envalid"
7-
import { fromRoot, TEST_SITES } from "./util"
7+
import { TEST_SITES } from "./util"
88
import handler from "serve-handler"
9+
import Table from "cli-table3"
10+
import * as color from "ansi-colors"
11+
912

1013
dotenv.config()
1114
const env = envalid.cleanEnv(process.env, {
12-
WEBPACK_PORT: envalid.port(),
15+
WEBPACK_PORT: envalid.port()
1316
})
1417

1518
const execSh = execShCb.promise
1619

20+
/**
21+
* Resolve a path relative to package root.
22+
*/
23+
const fromRoot = (...pathFromRoot: string[]) => {
24+
return path.join(__dirname, "../", ...pathFromRoot)
25+
}
1726
interface HugoOptions {
1827
baseURL: string
1928
themesDir: string
2029
config: string
2130
destination: string
2231
}
2332
const hugo = (hugoOptions: HugoOptions, execOptions: ExecOptions) => {
24-
const flags = Object.entries(hugoOptions).map(([key, value]) => `--${key}=${value}`).join(' ')
25-
return execSh(`hugo ${flags} --verbose`, execOptions)
33+
const flags = Object.entries(hugoOptions)
34+
.map(([key, value]) => `--${key}=${value}`)
35+
.join(" ")
36+
return execSh(`yarn hugo ${flags} --verbose`, execOptions)
2637
}
2738

2839
const buildSite = (name: string, configPath: string) => {
29-
return hugo({
30-
themesDir: fromRoot("./"),
31-
destination: fromRoot(`./test-sites/${name}/dist`),
32-
baseURL: `http://localhost:${env.WEBPACK_PORT}`,
33-
config: configPath
34-
}, {
35-
cwd: fromRoot(`./test-sites/${name}`)
36-
})
40+
return hugo(
41+
{
42+
themesDir: fromRoot("./"),
43+
destination: fromRoot(`./test-sites/${name}/dist`),
44+
baseURL: `http://localhost:${env.WEBPACK_PORT}`,
45+
config: configPath
46+
},
47+
{
48+
cwd: fromRoot(`./test-sites/${name}`)
49+
}
50+
)
3751
}
3852

3953
const setupTests = async () => {
54+
const table = new Table({
55+
head: ["Site", "URL"],
56+
style: { head: ["cyan", "bold"] }
57+
})
58+
4059
const sites = Object.values(TEST_SITES)
4160
await Promise.all(sites.map(site => buildSite(site.name, site.configPath)))
4261
sites.forEach(site => {
43-
http.createServer((request, response) => {
44-
return handler(request, response, {
45-
public: fromRoot(`./test-sites/${site.name}/dist`)
62+
http
63+
.createServer((request, response) => {
64+
return handler(request, response, {
65+
public: fromRoot(`./test-sites/${site.name}/dist`)
66+
})
4667
})
47-
}).listen(site.port)
68+
.listen(site.port)
69+
table.push([site.name, `http://localhost:${site.port}`])
4870
})
71+
72+
console.log([
73+
"\n",
74+
color.bold("Now serving the following sites:\n"),
75+
table.toString(),
76+
"\n"
77+
].join(""))
4978
}
5079

5180
export default setupTests

0 commit comments

Comments
 (0)