Getting Started Contributing Code to WordPress Gutenberg in Laragon

7 minute read

This post documents how I setup WordPress Gutenberg for local development on Windows 7, using Laragon instead of Docker, and made my first pull request to the project.

Most folks wanting to test and modify WordPress’ Gutenberg use Docker to manage their environment. That’s great because it’s especially consistent. But I’ve been happy using Laragon and wanted to attempt to avoid setting up yet another dev environment.

My Environment

I use Laragon, which runs great on Windows without needing a virtual machine (similar to XAMPP or WAMP). It comes with PHP, Apache and Nginx, Node, NPM, Git, and basically everything you need to run Gutenberg.

Setting Up a Local Test Site and Gutenberg Repo

  • I forked the Gutenberg GitHub repository.
  • I created a new local WordPress site using Laragon’s Quick App feature.
  • Then, in the new site’s wp-content/plugins/ folder, I opened Laragon’s Terminal and ran git checkout git@github.com:mnelson4/gutenberg.git to checkout my fork of Gutenberg
  • I needed to change the Node version, as the version that came with Laragon was too old. Although Laragon doesn’t come with NVM, there’s a good tutorial showing how to do that in a few minutes.
  • I activated the Gutenberg plugin, which instructed me to run npm install and npm run build which I did. They took a little while but worked out ok.

After all this, I could use Gutenberg fine, and modify its source files to my liking.

Making a Pull Request

After making a fix, I wanted to create a pull request to get my changes into the master branch of Gutenberg. For that,

  • I made my changes to my local checkout of Gutenberg
  • ran git checkout -b my-branch to create a new branch in Git, called “my-branch”. Maybe this is unnecessary, but it seems like good practice to create a branch for changes, even in your own fork.
  • ran git add {filepath} for each file I changed that I wanted to include (I didn’t use git add * because when I ran `npm run build` earlier, it made changes to package.json which I didn’t think I wanted to push)
  • then git commit -m "adds my great stuff" to create a local commit
  • then git push --set-upstream origin my-branchto push my changes to my GitHub fork of Gutenberg
  • went to the main Gutenberg GitHub repo’s page for creating a new pull request
  • on that page I needed to click “compare across forks”, then selected my fork, then my branch, then created the pull request and entered all the requested info

Then I had my first Gutenberg pull request! It was received pretty well, but it was suggested I should add an “e2e test”… which I guessed meant “end-to-end” testing. It was time to figure out what that meant!

Getting End-to-End Tests Working

Overview of Tools for Gutenberg’s Automated Testing

Gutenberg has a ton of automated tests:

  • PHPUnit tests for PHP code
  • Jest tests for Javascript code
  • Jest + Puppeteer tests for end-to-end testing (simulates a person manually testing from the browser)

So, I wanted to create a new Jest + Puppeteer test. Actually, first I wanted to make sure I could run those tests.

How Running Tests Without Docker Should Work

Gutenberg’s docs say you should be able to just run

WP_BASE_URL=http://localhost:8888 WP_USERNAME=admin WP_PASSWORD=password npm run test-e2e

Replacing `http://localhost:8888` with the URL of the local test site, which in my case was http://laragongut.test. But of course, that syntax is meant for Unix-based systems, not Windows. So on Windows I had to first run

SET WP_BASE_URL=http://laragongut.test

Notice that URL isn’t in quotes! If you do that, you’ll be told it’s invalid.

Likewise, I ran another command to set WP_USERNAME and WP_PASSWORD

Errors Running Tests Without Docker

But when I ran the tests, I got a long ugly error, ending in

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! gutenberg@3.9.0 pretest-e2e: concurrently "./bin/reset-e2e-tests.sh" "npm run build" npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the gutenberg@3.9.0 pretest-e2e script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

For the record, `npm run test-e2e` runs the command listed in package.json inside the scripts property with the name test-e2e. That command which begins a cascade of other commands, which eventually got to the scripts property’s pretest-e2e script, which is `concurrently “./bin/reset-e2e-tests.sh” “npm run build”`.

That last command uses concurrently to run two commands, one of which is ` ./bin/reset-e2e-tests.sh`. If you open up that bash file, you’ll see a bunch of Docker-specific logic in the (helpful) comments, like requiring a Docker instance to be running. That obviously won’t do, because I’m not using Docker.

I created an issue in GitHub documenting this error, but the fix was to change `concurrently “./bin/reset-e2e-tests.sh” “npm run build”` inside package.json to just `npm run build`, like in this commit. Then the tests proceeded ok for me (although I didn’t create a pull request for this change, because I realize the Docker setup expects that to run).

Edit: I was instructed that in the above-mentioned GitHub issue that I could instead use
npx wp-scripts test-e2e --config test/e2e/jest.config.json
to run the tests. I tested that out and it works fine 👍.

Timeouts During Tests

I see a ton of failures, some of which mention timeouts. So maybe my system is a bit old (yes it is, it’s getting toward 7 years old I think). It seems if you set the environment variable SET PUPPETEER_HEADLESS=false it doesn’t time out (I got the idea from this stack overflow question.) This also makes an actual Chrome browser open up, that you can see, and you can see it in action. 😑

Copying Over Plugins

Some of the failures were because the end-to-end tests expected certain plugins to exist in the WordPress site which didn’t.

I read in the Docker configuration file that it copies Gutenberg’s tests/e2e/test-mu-plugins into the WordPress site’s wp-content/mu-plugins; and Gutenberg’s tests/e2e/test-plugins into the WordPress site’s wp-content/plugins/gutenberg-test-plugins. So I did that manually, then a lot more tests passed.

Running a Specific Test

It takes forever to run the entire test suite, so I wanted to usually just run a single test at a time. To do that, I cd‘d to the topmost gutenberg folder, then ran wp run test-e2e test/e2e/specs/new-post.test.js (ie, included the filepath to the test I wanted to run). And, luckily, that did indeed only do that one test!

I couldn’t find it documented anywhere, but any extra arguments you pass into wp run test-e2e get forwarded onto however jest is finally called. So, with jest you can request to only test one file by doing jest path-to-file.

Writing My Own End-to-End Test with Jest and Puppeteer

To get started writing my own Jest + Puppeteer test, I skimmed over each’s getting started documentation, then tried to copy everything I could from the tests already written, like test/e2e/specs/new-post.test.js.

So I created a new test file, in test/e2e/specs/new-post-default-content.test.js, and also a new plugin in test/e2e/test-plugins/default-post-content.php.

The plugin changes the editor’s default title, content, and excerpt. The test activates that plugin, then verifies it successfully modified the editor’s content.

Gotcha: at one point, I used SET WP_BASE_URL=... to attempt to change the system variable. It didn’t change it at all. You need to just close down all the terminals, and open new ones, and it will get reset.

Committing the End-to-End Test

My addition of the end-to-end test was more substantial, and affected actual javascript code (well, Javascript test code anyway). So when I tried to commit it like before, it noticed some code style errors and refused the commit.

Ie, I saw

husky > npm run -s precommit (node v10.5.0)
25lRunning tasks for packages/*/package.json [started]
Running tasks for *.scss [started]
Running tasks for *.js [started]
Running tasks for {docs/{root-manifest.json,tool/*.js},packages/{*/README.md,*/s
ctors}.js,components/src/*/**/README.md}} [started]
Running tasks for packages/*/package.json [skipped]
→ No staged files match packages/*/package.json
Running tasks for *.scss [skipped]
→ No staged files match *.scss
Running tasks for {docs/{root-manifest.json,tool/*.js},packages/{*/README.md,*/s
ctors}.js,components/src/*/**/README.md}} [skipped]
→ No staged files match {docs/{root-manifest.json,tool/*.js},packages/{*/README.
s,selectors}.js,components/src/*/**/README.md}}
eslint [started]
eslint [failed]
→
Running tasks for *.js [failed]
→
25h× "eslint" found some errors. Please fix them and try committing again.
E:\Software\laragon\www\laragongut\wp-content\plugins\gutenberg\test\e2e\specs
ew-post-default-content.test.js
 4:8   error  A space is required after '{'                 object-curly-spacing
 4:16  error  A space is required before '}'                object-curly-spacing
 5:8   error  A space is required after '{'                 object-curly-spacing
 5:41  error  A space is required before '}'                object-curly-spacing
 7:9   error  There must be a space inside this paren       space-in-parens
 8:11  error  There must be a space inside this paren       space-in-parens
 9:23  error  There must be a space inside this paren       space-in-parens
 9:68  error  There must be a space inside this paren       space-in-parens
10:3   error  There must be a space inside this paren       space-in-parens
12:12  error  There must be a space inside this paren       space-in-parens
14:3   error  There must be a space inside this paren       space-in-parens
16:10  error  There must be a space inside this paren       space-in-parens
17:25  error  There must be a space inside this paren       space-in-parens
17:70  error  There must be a space inside this paren       space-in-parens
18:3   error  There must be a space inside this paren       space-in-parens
20:4   error  There must be a space inside this paren       space-in-parens
20:51  error  Block must not be padded by blank lines       padded-blocks
24:4   error  There must be a space inside this paren       space-in-parens
24:12  error  There must be a space inside this paren       space-in-parens
27:9   error  'content' is assigned a value but never used  no-unused-vars
27:31  error  There must be a space inside this paren       space-in-parens
27:49  error  There must be a space inside this paren       space-in-parens
28:9   error  'excerpt' is assigned a value but never used  no-unused-vars
28:31  error  There must be a space inside this paren       space-in-parens
28:63  error  There must be a space inside this paren       space-in-parens
29:9   error  There must be a space inside this paren       space-in-parens
29:15  error  There must be a space inside this paren       space-in-parens
29:21  error  There must be a space inside this paren       space-in-parens
29:40  error  There must be a space inside this paren       space-in-parens
30:3   error  There must be a space inside this paren       space-in-parens
31:2   error  There must be a space inside this paren       space-in-parens
✖ 31 problems (31 errors, 0 warnings)
29 errors, 0 warnings potentially fixable with the `--fix` option.
25h
husky > pre-commit hook failed (add --no-verify to bypass)                      

Which is kinda cool- it verifies the code meets the project’s code style guidelines, or else rejects it (although it does give you the option to workaround it).

Rather than go through that list of code style errors and fix them manually, I discovered a script I could use. I ran npm run lint-js:fix (mentioned in the project’s package.json file) and it took care of automatically fixing nearly all the code style issues 😃 .

Then I tried the commit again, and it worked.

Javascript That was All New To Me

For someone who’s been doing mostly PHP for the last several years, there was a lot of alien-looking Javascript. Here’s some highlights:

  • const means a constant “variable”, so it can’t be reassigned in the current scope
  • const [myConstant] = some_function() will expect some_function() to return an array, and it stuffs its first result into the new variable myConstant
  • `const foobar = await getEditedPostContent()` is a weird one. The function `getEditedPostContent()` doesn’t just return a regular value, it returns a Promise… which is a bit asynchronous weirdness. But adding `await` before it turns it back into a synchronous, normal function. Specifically, normally promises allow the function to complete before it has a return value, but await tells it to instead forget all that asynchronous nonsense, and not proceed until the function has a regular return value, and then stuff it into the variable foobar
  • `page.$eval(selector)` is a Puppeteer function that searches the current page using the CSS selector;  `page.$x(…)` is also a Puppeteer function that does the same thing but uses xpath

Ok I’m Done Now

Good luck with getting started contributing to Gutenberg! Let me know if you have any questions in the comments, or what struggles you’ve had. Also, the folks in WordPress Slack’s “core-editor” channel might be helpful.

2 thoughts on “Getting Started Contributing Code to WordPress Gutenberg in Laragon

Leave a Reply