AngularJS E2E Testing for the New Locations Section

The new Locations section of nypl.org is built with AngularJS, a JavaScript framework in which testing is integrated into the application development process. As the project involved large data sets (92 NYPL locations, each with their own events, exhibitions, and blogs), we needed to verify the correctness of our code and the overall application. Also, since AngularJS was new to us in the Digital Experience department, we wanted to implement best practices and that included writing tests. Although we've written tests for other JavaScript components, many of the concepts and techniques we encountered were new to us.

Unit tests, which directly test the code, were written. But, to test user interaction we used Protractor, AngularJS's end-to-end (E2E) testing framework. Protractor is a Node.js program that runs tests against the application in a browser and interacts with the page to simulate user interaction. What follows is a brief overview of how we used Protractor to run E2E tests on the Locations application. We wanted to share the testing techniques we learned in AngularJS.

A JavaScript configuration file is needed to run the specs. It's a simple file that tells Protractor the Selenium address, what browser to run the tests on, the spec files, and other tools we want to use before or during the tests.

After starting a Selenium server, we can run all the tests or select a specific section based on the 'suites' defined in the configuration file. This helped us break down all the E2E tests into smaller components and made it easier to run the tests and read the results.

Writing E2E tests

E2E tests are written using the Jasmine JavaScript testing framework. It is a behavior-driven development framework. It breaks up testing suites with a 'describe' function and tests within that suite with 'it' functions. Assertions and expectations go within the 'it' functions.

A 'describe' function can have optional beforeEach and afterEach functions that run before and after every 'it' function, respectively. The beforeEach is useful to group together tests that run on one page, such as the example above.

Page Objects

Using Page Objects is a pattern used to organize the tests and reuse DOM selectors in multiple tests. In the example above, if we wanted to test the location's name in a different test, the same line of code 'var title = element(by.css('.location-name'));' would be reused. This is troublesome if you have multiple tests checking the '.location-name' class. With Page Objects, we can define all the elements on a page and reuse them throughout different sets of tests.

Testing Bibliocommons Sign-On

One component of the nypl.org Drupal site that we had to port over to AngularJS was the Bibliocommons login button in the header. This component was rewritten as an AngularJS module so it can be used in other AngularJS applications. The module has a service that manages cookies and a directive for the login form and the post-login menu. Protractor can manage browser cookies and we took advantage of setting and getting cookies to mock an NYPL patron logging in and remaining logged in as they navigate through the site.

Testing Google Analytics

For web analytics, we used Google Analytics along with the AngularJS Angularitics plugin. It was easy to set up and configure but we wanted to write tests for page and event tracking. Google Analytics sets up a global 'ga' function which we override before each test, add the tracking events to an array when they are performed, and then verify that the tracking occurred.

To do so in Protractor, the executeScript() function was used to write and execute JavaScript to the browser to override the global 'ga' function. When we visit a different page, we verify that the page view was added to the array we created.

Similarly, event tracking was tested by having Protractor click on buttons or links and then verifying that the click event was logged. All Google Analytics tracking events are logged in the same array structure: ['send', 'event', 'Category title', 'Action title', 'Label'].

Mocking HTTP Requests

E2E tests are useful for verifying that the complete app works as intended, from the front-end to the API. After many discussions, we decided to also mock the API responses for some E2E tests, with the option to disable the mocked data with the real API data response.

In order to do so, we used browser.addMockModule(...) to create an AngularJS module to intercept the HTTP request with help from the ngMockE2E mock module. When creating the mock module, the $httpBackend service is available and it can be used to intercept HTTP requests and respond with our mocked data, while allowing all the other requests to pass through.

The mocked data serves as specification of what the API response should look like. When testing with mocked data, we are ensuring that the front-end is working as intended. When we disable the mocked API response, the E2E tests make sure that the API is returning the data correctly. Having control over the mocked API response also helps when we want to find out what happens to the interface when data is missing from the API. For example, we can remove a library's blog post from the API response and make sure that the intended fallback is working. In this specific example, we would verify that the "BLOGS" section and title on the library's page are not appearing since there are no blogs to display.

Note: Since we are using JSONP when calling the API, we must use the whenJSONP() method of $httpBackend and add '?callback=JSON_CALLBACK' at the end of the call.

The module can be added in the 'it' function for a test. But, since we are mocking the data for multiple tests, it is added in the beforeEach function that executes before every test.

Homepage Tests

The homepage for the new Locations section was the most involved page to test. There are many features and a huge data set to work with.

Geolocation, the browser's ability to get a user's current location, was mocked using the similar technique as the Google Analytics mocking. The geolocation function was mocked and we passed in the coordinates or error code we wanted to test.

This was a brief overview of all the E2E tests written for the Locations section and does not include the other hundreds of tests for the amenity pages, the header and footer elements, the Ask NYPL chat widget, or the autofill search feature on the homepage. Writing reusable components helped us the most to test different modules which ran on multiple pages. Next up we'll discuss unit testing and code coverage.

Comments

Patron-generated content represents the views and interpretations of the patron, not necessarily those of The New York Public Library. For more information see NYPL's Website Terms and Conditions.

Very cool!!

Very cool!!