DEV Community

Morcatko
Morcatko

Posted on

Synctractor - Testing React/Vue apps with Protractor

Protractor

Probably every frontend developer have heard about Protractor. An end-to-end test framework for Angular. There are many other similar frameworks. However Protractor has one great feature when testing Angular application. It automatically waits for your website to be ready. It does not test your website in the middle of loading. Protractor knows when to wait and when to test.

Protractor can be used with any website. No matter if it is written in Angular, React, jQuery or a static html. To be able to do that you have to disable a synchronization in Protractor config file

onPrepare: function() {
    browser.ignoreSynchronization = true;
}
Enter fullscreen mode Exit fullscreen mode

This disables waiting forcing Protractor to test as quickly as possible, even before your page fully loads and … most likely it will start failing.

The workaround is to do the waiting manually.

await browser.get("/login")
await $("#username").sendKeys("user");
await $("#password").sendKeys("password");
await $("#loginBUtton").click();
expect(await $("#message).getText()).toEqual("Welcome 'user'");
Enter fullscreen mode Exit fullscreen mode

Code that is very clear and contains only user actions and expectations must be extended with waits, sleeps and timeouts

await browser.get("/login")
// Wait for page load by checking presence of login button
await browser.wait(EC.presenceOf($(#loginButton)));
await $("#username").sendKeys("user");
await $("#password").sendKeys("password");
await $("#loginButton").click();
// Wait for login call & new page render
await browser.sleep(2000);
expect(await $("#message).getText()).toEqual("Welcome 'user'");
Enter fullscreen mode Exit fullscreen mode

This works but is very fragile. browser.sleep waits 2 seconds. In that time the user will be most likely logged in (or maybe not). The usual "fix" is to use very long sleeps, wait for some specific page elements, or markers that your app injects into a page when it is ready or similar workarounds.

You might be wondering how is it possible that it is so easy with Angular and so complicated with other frameworks. Well Protractor has actually two parts. One is the Protractor itself and the other piece is in Angular framework. These two parts communicate together during a running E2E test and ensure that everything works.

Synctractor

There comes synctractor. A library that allows you to use Protractor with non-Angular apps (react, vue) and rely on build-in synchronization and automatic waiting. It wraps asynchronous calls (fetch, setTimeout) and provides needed information for Protractor during a test run by emulating the Angular part.

It is easy to use

  1. install it npm i -save synctractor
  2. Add this to the very first line of your app entry point
    import * as synctractor from 'synctractor'; 
    synctractor.init();
    synctractor.monitorFetch();
    synctractor.monitorTimeout((_, t) => t !== 11000);
Enter fullscreen mode Exit fullscreen mode

(see github for explanation of the magic number 11000)

That is it. You can remove browser.ignoreSynchronization = true; from your Protractor config file and all sleeps from your spec files. Protractor will communicate with your app and wait when it is needed.

Check React and Vue examples in a synctractor repo

PS: Currently only fetch is supported. AJAX calls are not monitored and Protractor won't wait for it.

GitHub logo Morcatko / synctractor

Angular-Protractor synchronization for non-angular apps (React, Vue, ...)

Synctractor

Angular-Protractor synchronization for non-angular apps (React, Vue, ...) Using this library you can get rid of almost all browser.sleeps and browser.waits in your protractor tests and relly on the same synchronization mechanism that is used by protract and angular.

Quick Setup

  1. Install synctractor npm i --save synctractor
  2. Remove ignoreSynchronization from protractor config as it is not needed anymore
  3. Add this as the very first lines of your app entry point
    import * as synctractor from 'synctractor';
    synctractor.init();
    synctractor.monitorFetch();
    synctractor.monitorTimeout((_, t) => t !== 11000); 
    
    (see setTimeout details bellow for explanation of this magic number)

Manual Mode

There is automatic mode (synctractor.monitorXXX()) where you setup synctractor on your app entry point and that is all and there is also a manual mode, where you only initialize synctractor but you have to update calls all over your code. In automatic mode. you can get to unmonitored calls by synctractor.nativeXXX()

  1. …

Top comments (0)