Skip to content

set up e2e tests with playwright #941

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

Merged
merged 6 commits into from
Nov 5, 2022
Merged

set up e2e tests with playwright #941

merged 6 commits into from
Nov 5, 2022

Conversation

ChristopherChudzicki
Copy link
Contributor

@ChristopherChudzicki ChristopherChudzicki commented Oct 31, 2022

Pre-Flight checklist

  • Testing
    • Code is tested
    • Changes have been manually tested

What are the relevant tickets?

#919

What's this PR do?

This PR adds e2e testing via Playwright to OCW Hugo Themes.

There are a variety of other significant changes that I will try to call out in individual comments.

How should this be manually tested?

First: Run yarn install and read the readme: https://github.com/mitodl/ocw-hugo-themes/tree/cc/ci2/tests-e2e If you use VS Code, I highly recommend installing the Playwright extension. (See the Readme.).

  1. Run yarn test:e2e. It should fail. Add the required environment variable. (Use value 3001; see set up e2e tests with playwright #941 (comment) ).
  2. Run yarn test:e2e again. It should suceed but it will also be kinda slow.
  3. Run yarn start:webpack. In a separate shell, run yarn test:e2e. It should be pretty fast. (Now Playwright is reusing the webpack server.)
  4. Stick a .only on one of the tests, and run yarn test:e2e --debug. You can step through the test.
  5. Run yarn start:course and yarn start:www, too. I changed some stuff with environment variables and scripts. Those should both still work. You could also try OCW_TEST_COURSE=different-from-value-in-env-file yarn start:course. That should work now, whereas previously it would not.

Where should the reviewer start?

I suggest starting with the readme, then reading some of the test files. The test files should more or less make sense without context.

Then look at all the other junk.

@ChristopherChudzicki ChristopherChudzicki force-pushed the cc/ci2 branch 2 times, most recently from a2d7d87 to 19703f6 Compare October 31, 2022 19:59
@github-actions
Copy link

@github-actions
Copy link

github-actions bot commented Oct 31, 2022

Lighthouse results:

results for https://ocw-hugo-themes-www-pr-941--ocw-next.netlify.app/:

Accessibility Best Practices Performance Progressive Web App SEO
100 💯 83 😄 55 😟 20 😱 79 🙂

results for https://ocw-hugo-themes-www-pr-941--ocw-next.netlify.app/search/:

Accessibility Best Practices Performance Progressive Web App SEO
93 🎉 92 🎉 70 🙂 30 😰 93 🎉

results for https://ocw-hugo-themes-course-v2-pr-941--ocw-next.netlify.app/:

Accessibility Best Practices Performance Progressive Web App SEO
74 🙂 100 💯 72 🙂 30 😰 79 🙂

@ChristopherChudzicki ChristopherChudzicki force-pushed the cc/ci2 branch 6 times, most recently from c67d525 to bc449de Compare November 1, 2022 21:32
@@ -3,31 +3,21 @@
set -euo pipefail

source package_scripts/common.sh
load_env --require-dot-env
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got rid of load_env entirely. This function made it very hard to specify variables manually. It was less like "load env" and more like "forcefully override any environment variables that were already defined; replace them with the content of .env"

So now:

  • yarn start:course will by default use your env variables from .env
  • but OCW_TEST_COURSE=whatever yarn start:course will use OCW_TEST_COURSE as the value of that variable . I.e., now we can override them at the command line.

The downside is that running ./package_scripts/start_course.sh by itself in your shell will now probably fail, because...environment variables are missing. I'm sure there's some way to source your env vars if you wanted to, though. (In yarn scripts, we run it via dotenv-cli.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think we added this a while back because we were having trouble with dotenv. Like I mentioned above, I didn't realize that the dotenv-cli package was required and that you had to prefix any commands that needed the .env file with it. They don't mention it anywhere on the dotenv Github and it's kind of puzzling why the "CLI" component of that is a separate package. Either way, it works much better so yea it makes sense to get rid of this.

@@ -1,9 +1,10 @@
{{- $port := 3001 -}}
Copy link
Contributor Author

@ChristopherChudzicki ChristopherChudzicki Nov 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This $port should be $port := getenv "WEBPACK_PORT". All other instances of 3001 have been replaced with the environment variable.

The reason I left this one hard-coded is that in order to access the environment variable with hugo we need to whitelist it in the config, which requires a PR to ocw-hugo-projects.

I wanted CI to pass, so for now I have left this as instance as 3001.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the hugo projects PR: mitodl/ocw-hugo-projects#222, and https://github.com/mitodl/ocw-hugo-themes/tree/cc/ci3 branch of themes includes the required change.

Comment on lines +3 to +5
<section class="course-detail-section">
{{ partial "course_description.html" (dict "context" .) }}
</div>
</section>
Copy link
Contributor Author

@ChristopherChudzicki ChristopherChudzicki Nov 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Site change! very minor

Changing div -> section here made seemed reasonable and made selecting "Course Info" section in Playwright a little easier. See testing-library/dom-testing-library#403 (comment) for a related issue. (I believe that issue is where I picked up the pattern of using .closest() for stuff like this. 🤷 )

"start:www": "concurrently \"npm:start:webpack\" \"npm:start:www:hugo\" --kill-others",
"start:course": "concurrently \"npm:start:webpack\" \"npm:start:course:hugo\" --kill-others",
"start:fields": "concurrently \"npm:start:webpack\" \"npm:start:fields:hugo\" --kill-others",
"start:webpack": "dotenv -- webpack serve --config ./base-theme/assets/webpack/webpack.dev.ts --hot --host 0.0.0.0 --stats-children",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dotenv-cli. It loads env vars on the cli.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like an idiot now because I didn't realize this package was required for NPM to pass env variables to package scripts. I thought all you needed install was the dotenv package and that would happen, but apparently I was mistaken.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually had never used this package before; I have used

 node -r dotenv/config your_script.js

(See https://www.npmjs.com/package/dotenv#Preload) but that only works if you're calling node.

Comment on lines 10 to 12
const env = envalid.cleanEnv(process.env, {
WEBPACK_PORT: envalid.port()
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is https://www.npmjs.com/package/envalid. It's nice for (a) validating that env variables are set and providing good error messages if they are not, and (b) typescript. Since process.env.WHATEVER is string | undefined by default.

I've scattered a few envalid calls into the repo. I think one common usage of envalid is to have a single file that validates the environment variables and sets defaults and stuff. (Like settings.py in Django, I guess). I think that would be really nice, but we'd have to have some centralized js-based cli through which we call hugo (Rather than our current shell scripts). I briefly explored doing something like that with oclif, but it seemed like way too big a change for the moment.

Aside: oclif is cool. And it's built by Salesforce / it's what the heroku CLI is built upon. And while I like the idea of a centralized CLI for this repo, like django provides us elsewhere, I would not recommend oclif because https://npmtrends.com/commander-vs-oclif-vs-yargs

@@ -3,7 +3,6 @@
set -euo pipefail
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gumaerc Do you think we need this script anymore?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A while back I had been using this script to build all courses locally, and even before that it was actually being used in an EC2 box to deploy the whole site before we had ocw-studio and Concourse. Now that we have a full dev setup for ocw-studio that can build the whole site locally, I think we can safely remove this without consequence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless someone wants it here, I think I'll drop that script in a separate PR.

@@ -0,0 +1 @@
# ocw-ci-test-www
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not written very many tests for www yet, and this is the only file in the test site.

Really I included www mostly as a proof-of-concept for building / running tests against multiple sites using multiple hugo configs.


### Is there a `--watch` mode for re-running tests?

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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For VS Code users, ... Individual tests can be run from your editor with the click of a button.

I swear I'm not just a shill for Microsoft.

@pt2302
Copy link
Contributor

pt2302 commented Nov 2, 2022

During testing, found out we first need to run npx playwright install.

@pt2302
Copy link
Contributor

pt2302 commented Nov 2, 2022

Runs in my tests with

url: `http://localhost:${env.WEBPACK_PORT}/static/css/main.css`,
changed to http://0.0.0.0:${env.WEBPACK_PORT}/static/css/main.css.

Copy link
Contributor

@pt2302 pt2302 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

Comment on lines +16 to +30
testDir: './tests-e2e',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
timeout: 5000
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
actionTimeout: 0,
trace: 'on-first-retry',
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW: Most of these settings are what Playwright recommends. I created this config file via (https://playwright.dev/docs/intro#installing-playwright)

yarn create playwright

and these were the settings it gave me. My customizations were:

  • removing some comments because it seemed cluttery and TS docstrings / playwright docs have the same information.
  • removing the extra browsers (firefox, webkit)
  • customizing the webServer portion.

We could restore firefox + webkit. I'm indifferent. I'm not sure how useful the extra browsers would be at the moment; maybe if we were doing visual or very interactive tests.

"packageManager": "[email protected]"
"packageManager": "[email protected]",
"devDependencies": {
"@playwright/test": "^1.27.1"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW: I mentioned on a zoom call once that there is playwright-testing-library akin to other libs we use like react-testing-library (all part of the Testing Library family).

I was initially using it, but decided to drop it. Playwright itself introduced some features inspired by testing-library, and the creator of playwright-testing-library says 1:

Start out without playwright-testing-library and see how the new Testing Library API in 1.27 serves y

Footnotes

  1. https://github.com/testing-library/playwright-testing-library/issues/558#issuecomment-1274195339w

Copy link
Collaborator

@gumaerc gumaerc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this looks great and is a good start to getting some actual testing done here. Everything works, I just have one request regarding the WEBPACK_PORT environment variable:

@@ -18,7 +23,7 @@ const devOverrides: Configuration = {
devtool: "eval-source-map",

devServer: {
port: process.env.PORT || 3001,
port: env.WEBPACK_PORT,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not env.WEBPACK_PORT || 3001? I get an error if I try to run yarn test:e2e without WEBPACK_PORT explicitly in my env. We default it to 3001 when running a site for dev, so why wouldn't we do that for tests?

Copy link
Contributor Author

@ChristopherChudzicki ChristopherChudzicki Nov 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We default it to 3001 when running a site for dev

If we merge this PR as-is, that is not longer true.

But, to be sure, having a required WEBPACK_PORT env variable also annoys me. But I do not think we currently have a good way to set the default that is accessible in JS+Hugo, and the current situation on main illustrates this.

Currently on main:

So you can see the inconsistency: if you have PORT env var set, webpack will respect it but hugo will not. 💥.

Here are some options that occur to me:

  1. Hardcode 3001 everywhere. I don't really want to do this, but it doesn't actually seem terrible

  2. Use an environment variable with no default. If it's not set, you get an error message. A little annoying.

  3. Use an environment variable with inlined-defaults. This is the current situation, and IMO it is worse than (1) or (2), because of the possibility for inconsistency, as demonstrated by current maint branch.

  4. Somehow have a "single source of truth" for environment variables that also allows setting defaults... I can think of two ways:

    • 4A. Build a CLI... call it ocw. It would be like ./manage.py in django. We'd use it like:

      yarn ocw --help
      yarn ocw test
      yarn ocw test-e2e
      yarn ocw build course        <-- build a course; we'd use this in pipelines
      yarn ocw build course --help <-- get help info for all the flags/args to build
      yarn ocw serve course        <-- serve a course in dev mode
      

    where the ocw CLI would do something like load environment variables, set default values for some, provide useful error messages, have docstrings...like manage.py does.

    This is appealing to me for a variety of reasons. Setting env variables is one. Another is it's a way to provide documentation without cluttering the README.

    • 4B. Another option is to have .env and .env.defaults and

      # replace this
      yarn dotenv <cmd>
      # with this:
      yarn dotenv -e .env -e .env.defaults <cmd>

      I don't dislike this idea. But then do we have three files? (env, env.defaults, and env.sample)? Or do we just use env.sample in place of env.defaults? I'm not sure all the values in .env.sample are reasonable as defaults.

Copy link
Contributor Author

@ChristopherChudzicki ChristopherChudzicki Nov 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, submitted that too earlier...

In summary: 4A is my longer-term preference, but the approach of this PR is (2) which seems ok. Maybe 4B is good.

What do you think? (of these options, or of some other idea)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think 3 is the best option specifically referring to the port. There aren't that many places where it's used to generate inconsistency, and I have never heard of software that spins up a service that binds to a port requiring you to specify said port without a default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There aren't that many places where it's used to generate inconsistency,

But even with not many, we already have the inconsistency on main. Run PORT=3456 yarn start:course and observe that OCW does not load webpack assets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I'm going to try (3)... if I can set all the js defaults in one place, that's good enough. If the env var is needed in Hugo, too, we'll have to set the default in two places. If we can keep it to two places, I think that's ok.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However it's accomplished, Node and Hugo don't require you to set a port when you run them and will run on a default port. We shouldn't require that it be set either, but allow it to be overridden. I'd rather hard code 3000 for Hugo and 3001 for Webpack than require the env variable to be set to spin up the server.

"start:www": "concurrently \"npm:start:webpack\" \"npm:start:www:hugo\" --kill-others",
"start:course": "concurrently \"npm:start:webpack\" \"npm:start:course:hugo\" --kill-others",
"start:fields": "concurrently \"npm:start:webpack\" \"npm:start:fields:hugo\" --kill-others",
"start:webpack": "dotenv -- webpack serve --config ./base-theme/assets/webpack/webpack.dev.ts --hot --host 0.0.0.0 --stats-children",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like an idiot now because I didn't realize this package was required for NPM to pass env variables to package scripts. I thought all you needed install was the dotenv package and that would happen, but apparently I was mistaken.

@@ -3,31 +3,21 @@
set -euo pipefail

source package_scripts/common.sh
load_env --require-dot-env
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think we added this a while back because we were having trouble with dotenv. Like I mentioned above, I didn't realize that the dotenv-cli package was required and that you had to prefix any commands that needed the .env file with it. They don't mention it anywhere on the dotenv Github and it's kind of puzzling why the "CLI" component of that is a separate package. Either way, it works much better so yea it makes sense to get rid of this.

package.json Outdated
"start:webpack": "dotenv -- webpack serve --config ./base-theme/assets/webpack/webpack.dev.ts --hot --host 0.0.0.0 --stats-children",
"start:webpack": "dotenv -- webpack serve --config ./base-theme/assets/webpack/webpack.dev.ts --hot --stats-children",
Copy link
Contributor Author

@ChristopherChudzicki ChristopherChudzicki Nov 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For posterity: @pt2302 and @ChristopherChudzicki on M1's reproducibly had an issue where:

  • all of ocw works fine on both node 16 and node 18, with one exception...
  • yarn test:e2e, which runs yarn start:webpack, did not work on node 18.

Removing --host 0.0.0.0 here fixes the issue for me.


I am not sure why we had --host 0.0.0.0 here in the first place. Based on https://stackoverflow.com/questions/35412137/how-to-get-access-to-webpack-dev-server-from-devices-in-local-network, I think this was probably set to enable testing OCW on phones.

But that doesn't seem to be working for me. Both my computer and my phone are on the same wifi network, and my phone cannot access http://localhost:3001/static/css/main.css.

So I think we should just remove this.

@gumaerc Do you know if there is any good reason to leave --host 0.0.0.0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that with this change, it runs for me on Apple Silicon using Node 18.14. @ChristopherChudzicki suggested that this might be due to a security setting on M1.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ChristopherChudzicki Hitting http://localhost:3001 on your phone is going to try and access port 3001 on your phone, but your phone isn't running a web server. Binding to 0.0.0.0 means that you can hit the local IP address of your dev machine from any other device on your network. Say your dev machine has a local IP of 192.168.1.100. That means that if you run this with --host 0.0.0.0 then on your phone you can go to http://192.168.1.100:3001 and you will get your webpack instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gumaerc Awesome, thank you for that explanation! Sounds like we do want 0.0.0.0. I've reverted the "replace 0.0.0.0 with localhost" for now.

@ChristopherChudzicki ChristopherChudzicki force-pushed the cc/ci2 branch 3 times, most recently from dbc8a01 to ed29972 Compare November 4, 2022 18:31
Copy link
Contributor Author

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gumaerc I believe that the most recent commit (c9cc0ee) has all changes since your last review. Aside from a rebase with no conflicts.

I went ahead and set a default port in a 4 places. I'm not super happy with this and I plan to make a small folllowup PR that reorganizes a few env variable things, but I'd rather not add even more env-reorganization to this PR.

Thanks again for your explanations about 0.0.0.0 (I reverted the 0.0.0.0 --> localhost change for now; will also address that in my incoming PR).

Anyway, try: start:www, start:course, and test:e2e without a WEBPACK_PORT explicitly defined. Should work.

Copy link
Collaborator

@gumaerc gumaerc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants