With a long list of end-to-end (e2e) test frameworks available to choose from, it’s hard to know which one you should be using. Cypress and Selenium are leading the market as the most widely used options, but there’s also Appium for mobile app testing, Puppeteer for automating tasks in Chrome, and Protractor for Angular and AngularJS applications, just to name a few. Recently a newcomer has joined the pack: TestProject, a free, open-source test automation platform for e2e testing that helps simplify the web, mobile, and API testing. The TestProject SDK has language support for Java, C#, Python, and, most recently, JavaScript. This article will show how we can use the to test a React app with as our test framework. TestProject JavaScript OpenSDK Jest Ready to get started? App Overview To begin, let’s take a look at the demo app that we’ll be testing. This app is relatively straightforward: just a simple request form in which a user can enter their first name, last name, and email address. Demo app: request form If the form is submitted without being properly filled out, error messages are shown below each invalid input. Demo app: invalid input Upon successful form submission, the app shows some confirmation text. Demo app: filling out the form Demo app: confirmation page Simple enough, right? If you’d like to see the demo in action, you can or . find the demo app hosted here view the source code on GitHub Now, let’s look at how the app was made. Creating the React App As noted above, this app is written in React. To simplify the boilerplate code and dev tooling, I used the tool to bootstrap the app. create-react-app npx create-react-app testproject-demo With the skeleton app generated, I then removed the default app content and wrote a simple form component in a file called . Here’s the request form code reproduced in full: RequestForm.js React, { useState } RequestForm = { [firstName, setFirstName] = useState( ) [lastName, setLastName] = useState( ) [email, setEmail] = useState( ) handleFirstNameChange = { setFirstName(e.target.value) } handleLastNameChange = { setLastName(e.target.value) } handleEmailChange = { setEmail(e.target.value) } [firstNameError, setFirstNameError] = useState( ) [lastNameError, setLastNameError] = useState( ) [emailError, setEmailError] = useState( ) [submitted, setSubmitted] = useState( ) handleSubmit = { e.preventDefault() setFirstNameError(firstName ? : ) setLastNameError(lastName ? : ) setEmailError(email ? : ) (firstName && lastName && email) { setSubmitted( ) } } submitted ? ( ) : ( <div className={`formGroup${firstNameError ? ' error' : ''}`}> <label htmlFor="firstName">First Name</label> <input name="firstName" id="firstName" data-testid="firstName" value={firstName} onChange={handleFirstNameChange} /> </div> {firstNameError && ( <p className="errorMessage" id="firstNameError"> {firstNameError} </p> )} <div className={`formGroup${lastNameError ? ' error' : ''}`}> <label htmlFor="lastName">Last Name</label> <input name="lastName" id="lastName" data-testid="lastName" value={lastName} onChange={handleLastNameChange} /> </div> {lastNameError && ( <p className="errorMessage" id="lastNameError"> {lastNameError} </p> )} <div className={`formGroup${emailError ? ' error' : ''}`}> <label htmlFor="email">Email</label> <input type="email" name="email" id="email" data-testid="email" value={email} onChange={handleEmailChange} /> </div> {emailError && ( <p className="errorMessage" id="emailError"> {emailError} </p> )} <button type="submit" id="requestDemo"> Request Demo </button> </form> ) } import from 'react' import './RequestForm.css' export const => () const '' const '' const '' const => e const => e const => e const '' const '' const '' const false const => e '' 'First Name field is required' '' 'Last Name field is required' '' 'Email field is required' if true return Thank you! We will be in touch with you shortly. < = > p id "submissionConfirmationText" </ > p < = = > form className "requestForm" onSubmit {handleSubmit} As you can see, we have a function component that displays three inputs for the user’s first name, last name, and email address. There is a “Request Demo” submit button at the bottom of the form. When the form is submitted, error messages are shown if there are any invalid inputs, and a confirmation message is shown if the form is submitted successfully. That’s really all there is to the app. Now, on to the fun part. How can we configure our end-to-end tests with TestProject? Getting Started with TestProject To begin, we’ll need to first . After that, we can . There are options to either download the agent for desktop or for Docker. Which one you choose is up to you, but I chose to download the desktop app for Mac. You then need to to link your agent to your TestProject account. create a free TestProject account download the TestProject agent register your agent Next, we’ll to use in our project. Once we have a developer token, we’ll create an file in the root directory of our project and add the following line of code to store our token in the environment variable: generate a developer token .env TP_DEV_TOKEN TP_DEV_TOKEN=<YOUR DEV TOKEN HERE> You’ll note that we tell Git in our file to ignore our file so that our token or other environment secrets don’t get committed into our version control and accidentally shared with others. .gitignore .env Finally, we’ll need to install a couple of npm packages as to use the TestProject JavaScript OpenSDK in our app: devDependencies yarn add --dev @tpio/javascript-opensdk selenium-webdriver With that, we’ve laid most of the groundwork to begin using TestProject with our e2e tests. Configuring Jest Next up, we need to configure Jest. Since we used create-react-app to bootstrap our app, our project uses to run Jest and with some default configuration options. However, it’d be nice if we could configure Jest and add a few more npm scripts to be able to run unit tests and e2e tests separately. react-scripts React Testing Library To do this, I added the following npm scripts to the “scripts” section of my file. Each one contains some specific Jest CLI configuration options: package.json : { ...other scripts here : , : , : , : , : , : , : }, "scripts" "start" "react-scripts start" "test:e2e" "wait-on http://localhost:3000/testproject-demo/build/ && react-scripts test --testPathPattern=\"(\\.|/)e2e\\.(test|spec)\\.[jt]sx?$\" --testTimeout=30000 --runInBand --watchAll=false" "test:e2e:ci" "run-p start test:e2e" "test:e2e:watch" "wait-on http://localhost:3000/testproject-demo/build/ && react-scripts test --testPathPattern=\"(\\.|/)e2e\\.(test|spec)\\.[jt]sx?$\" --testTimeout=30000 --runInBand" "test:unit" "react-scripts test --testPathPattern=\"(\\.|/)unit.(test|spec)\\.[jt]sx?$\" --watchAll=false" "test:unit:coverage" "react-scripts test --testPathPattern=\"(\\.|/)unit.(test|spec)\\.[jt]sx?$\" --watchAll=false --coverage" "test:unit:watch" "react-scripts test --testPathPattern=\"(\\.|/)unit.(test|spec)\\.[jt]sx?$\"" That’s a lot to take in! Let’s break down each of these commands while highlighting some of the key pieces of this code. First, we see the script. That one is easy enough: it runs our app locally in development mode. This is important because e2e tests require the app to be running to work properly. start Next, we see the script. This command waits for the app to run locally on port 3000 before running any tests. It then uses the test command to run our app’s tests but with several Jest CLI configuration options applied. test:e2e react-scripts The option tells Jest to only run our tests that end in (and a few other variations). The option increases Jest’s default timeout of 5 seconds per test to 30 seconds per test since e2e tests take a little longer to run than simple unit tests. testPathPattern e2e.test.js testTimeout The option tells Jest to run our test files serially instead of in parallel since we only have one TestProject agent installed on our machine. runInBand And finally, the option makes it so that the tests do not run in “watch” mode, which is the default setting for Jest with react-scripts. Whew, that was a lot! watchAll=false The third script is . This command is a combination of the and commands to help simplify the testing process. In order to use the original command, we first must be running the app locally. test:e2e:ci start test:e2e test:e2e So we’d need to first run and then run . That’s not a huge deal, but now we have an even simpler process in which we can just run to both start the app and run the e2e tests. yarn start yarn test:e2e yarn test:e2e:ci The fourth script, , is very similar to the script but runs the tests in “watch” mode in case you want your tests to be continuously running in the background as you make changes to your app. test:e2e:watch test:e2e The last three scripts are for running unit tests. The script runs the unit tests with Jest and React Testing Library and only looks for tests that end in (and a few other variations). test:unit unit.test.js The script runs those same unit tests but also includes a test coverage report. And finally, the script runs the unit tests in watch mode. test:unit:coverage test:unit:watch This may seem like a lot of information to take in, but the takeaway here is that we’ve now created several helpful npm scripts that allow us to easily run our unit and e2e tests with short and simple commands. All of the hard configuration work is out of the way, so now we can focus on writing the actual tests. Writing Tests with the JavaScript OpenSDK We now have Jest and TestProject configured for our project, so we’re ready to write our first e2e test. End-to-end tests typically focus on critical workflows of the app represented by user journeys. For our request form, I can think of two important user journeys: when a user tries to submit an invalid form and when a user successfully submits a properly filled out form. Let’s write an e2e test for each workflow. Our complete file looks like this: App.e2e.test.js { By } { Builder } describe( , () => { testUrl = driver beforeEach( () => { driver = Builder() .forBrowser( ) .withProjectName( ) .withJobName( ) .build() }) afterEach( () => { driver.quit() }) it( , () => { driver.get(testUrl) driver.findElement(By.css( )).sendKeys( ) driver.findElement(By.css( )).sendKeys( ) driver.findElement(By.css( )).sendKeys( ) driver.findElement(By.css( )).click() driver .findElement(By.css( )) .isDisplayed() }) it( , () => { driver.get(testUrl) driver.findElement(By.css( )).click() driver.findElement(By.css( )).isDisplayed() driver.findElement(By.css( )).isDisplayed() driver.findElement(By.css( )).isDisplayed() }) }) import from 'selenium-webdriver' import from '@tpio/javascript-opensdk' 'App' const 'http://localhost:3000/testproject-demo/build/' let async await new 'chrome' 'TestProject Demo' 'Request Form' async await 'allows the user to submit the form when filled out properly' async await await '#firstName' 'John' await '#lastName' 'Doe' await '#email' 'john.doe@email.com' await '#requestDemo' await '#submissionConfirmationText' 'prevents the user from submitting the form when not filled out properly' async await await '#requestDemo' await '#firstNameError' await '#lastNameError' await '#emailError' In our first test, we ensure that a user can successfully submit the form. We navigate to our app’s url, use the method to enter text into the three input fields, and then click the submit button. We then wait for the confirmation text to appear on the screen to validate that our submission was successful. sendKeys You’ll note that all of the selectors look just like normal Selenium selectors. You will typically find elements using CSS selectors or using the XPath selector. In our second test, we ensure that a user is prevented from submitting the form when there are invalid inputs on the page. We first navigate to our app’s url and then immediately click the submit button without filling out any of the input fields. We then verify that the three error messages are displayed on the screen. You’ll also note that we’ve extracted out some of the shared test setup and teardown into the and blocks. In the block, we create our web driver for Chrome. In the block, we quit the driver. beforeEach afterEach beforeEach afterEach Running Our E2E Tests Here’s the moment of truth: Let’s try running our end-to-end tests. In our terminal, we’ll run to start the app and run the e2e tests. And… the two tests pass! You should see the app open on the Chrome browser, see the steps for each test be executed, and then see the test results back in the terminal: yarn test:e2e:ci Running our e2e tests in the terminal TestProject even provides its own free, built-in report dashboards as well as local HTML and PDF reports so that you can see the test results within the browser. This is perfect when viewing tests that are run as part of a CI pipeline. Here is my report after running the test suite twice: TestProject report dashboard Conclusion Well, we did it! We successfully wrote and ran end-to-end tests using React, Jest, and the TestProject JavaScript OpenSDK. Mission accomplished. The exciting thing is that this is just the tip of the iceberg. TestProject is full of other hidden gems like the ability to , a plethora of for commonly needed test actions, and for sending test reports notifications. write tests directly within the browser using a GUI add-ons integrations with apps like Slack Who knows what the future will hold for the world of end-to-end test automation, but TestProject is certainly a platform worth keeping your eye on.