Nginx all the things!

Developers are increasingly working across multiple projects, and we need a sane method of changing contexts within our local development environment very quickly – or running them all at once – to remain efficient. Keep reading to learn how to run an Nginx proxy on your local machine to shepherd requests to port 80 to other apps running on various ports, using the path of the request to determine which app to forward to.

Why would we wanna do that?  Because developers are increasingly working across multiple projects, and we need a sane method of changing contexts within our local development environment very quickly – or running them all at once – to remain efficient.  Our approach is to use an Nginx proxy to forward all requests on localhost:80 to our various applications, each running on their own unique port.

As coders make contributions to upstream dependencies and neighboring apps alike, and as we write more end-to-end automated browser tests that cross application boundaries, running a local Nginx proxy will be a requirement.

Ok, so let’s get you up and running …

Installing Nginx

We’ll use HomeBrew to install Nginx on our Mac.  This makes it easy to upgrade in the future, doesn’t require much manual configuration or running make, and doesn’t mess with any system files.

HomeBrew

Install nginx using the HomeBrew tap and formula.

Below is an example of the output you should expect after running this command, including some helpful tips for starting/stopping the server.

Configure Permissions

By default, Nginx will run on port 8080 so as to not require sudo.  But we’re going to want to run on port 80 to better simulate the production experience and route all requests properly, so we’ll need to set permissions on our directory appropriately.

Configure nginx.conf

You’ll need a robust configuration file to dynamically map incoming requests based on URL path to the appropriate apps running on different ports.  Many apps run on port 8080 by default, and some apps are easier than others to change, but we’ll need to run each app on its own port.

If you installed nginx via Homebrew, your nginx.conf file can be found at /usr/local/etc/nginx/nginx.conf.

Generate Self-Signed SSL Certificates

You’ll need to create a directory for your certs, then run the commands to generate them.  Notice these files are being created in the ssldirectory noted in your nginx.conf.

Start the Server

From your terminal, let’s start up nginx and make sure there are no errors returned:

Start Your Nginx Proxy

Each time you make changes to your nginx.conf file, you’ll need to reload the web server and ensure no errors were returned:

Reload Your Nginx Proxy

To stop the server, send the “stop” signal:

Stop Your Nginx Proxy

Start Up Your Apps

You should now be able start up each of your apps concurrently!  However, to do so you may still need to start those apps on the ports you specified with location directives, so check the your app’s README for how to do that.

As an example, within a typical Node.js app, it’s as simple as setting the port environment variable:

That’s the gist of it, we’re simply using Nginx to proxy all requests on port 80 to the various apps running locally on alternate ports.

Got questions, or even a better way to do any of this?  Please let me know in the comments section below!

WaitFor in WebDriver.io

Have you seen your WebDriver tests fail with vague errors or timeouts when trying to locate and interact with elements on your page?

Following the best practices in this post will help you minimize random failures while running automated browser tests via WebDriver.io and Selenium.

Have you ever seen your WebDriver test fail with generic errors such as element is not clickable at point, other element would receive the click, or maybe just a timeout when trying to locate an element on your page?

Well whether you have or you haven’t (yet), this post will help you minimize random failures while running automated browser tests via WebDriver.io and Selenium.

Best practices

There are some best practices in WebDriver.io that you should follow when interacting with elements on your pages.  Below are some suggestions for improving the quality and stability of your e2e tests.

Consider the following basic Page Object example which references a “Next” button and an “Email” input box:

You must wait for an element to be visible before you interact with it.

The example above has a function which simulates the “Next” button’s click, however, it doesn’t check to make sure the element is on the page.  In WebDriver, an element can exist, but not be visible, and therefore not able to be interacted with.

If our “Next” button is hidden by default, and perhaps only shown after a user interacts with other parts of the page, we’ll need to wait for the element to be shown before we click it using the element.waitForVisible() method.

Let’s improve our clickNextButton() function:

You must wait for an element to be enabled before you click or type.

There are times when you must wait for other conditions to be true, as well.  For example, we can’t call the setValue() method on an input element if it’s disabled, the same way a user can’t type in a disabled textbox.  We’ll use the element.waitForEnabled() accordingly.

Let’s improve our setEmailInputValue() function:

Above, we’ve now made the setEmailInputValue() more stable because our interaction method checks to be sure the textbox is both visible and enabled before it attempts to set the value.

Set custom timeouts when using waitFor*()

There is a default timeout set for your WebDriver session, but for certain operations, you may need to increase the timeout when waiting for elements.  In this case, simply specify the number of ms to wait as the first parameter, as shown below.

Scrolling to elements

There are also times when the element is off the page and must be scrolled to in order to click.  Consider using the element.scroll()function to ensure the element is within the viewport before interacting with it.

waitFor() all the things

There are plenty more waitFor*() functions built into WebDriver, just check out the API for a comprehensive list.  And should one of those convenient utility functions not fit your particular use case, you can further customize things by using the waitUntil() function to wait for any condition you like.

Next up: disabling CSS animations

In my next post, I’ll cover how to disable CSS animations in your app to improve stability and reduce the number of random failures in WebDriver.io.