Migrate Tests from Protractor to TestCafe

Protractor is an end-to-end testing tool for Angular/AngularJS applications. Since the Angular team announced plans to end support for Protractor by 2022 and not to include it in new projects, Protractor users are urged to migrate to an alternative testing solution.

This section describes how to adapt Protractor tests to be run on TestCafe. The code samples below illustrate the difference between how the same test looks on the Protractor and TestCafe API.

Protractor

describe('To do test', () => { 
  it('Correct new items', () => { 
    browser.get('http://todo.org/') 
    element(by.css('.todo-new-text')).sendKeys('Start migration to Testcafe'); 
    element(by.cssContainingText('button', 'Add')).click(); 
    expect(element(by.css('.todo-list')).all(by.css('.todo-item')).count()).toEqual(1); 
    element(by.css('.todo-new-text')).sendKeys('Finish migration to Testcafe'); 
    element(by.cssContainingText('button', 'Add')).click(); 
    expect(element(by.css('.todo-list')).all(by.css('.todo-item')).count()).toEqual(2); 
    expect(element(by.css('.todo-list')).all(by.css('li')).get(0).getText()).toEqual('Start migration to Testcafe'); 
    expect(element(by.css('.todo-list')).all(by.css('li')).get(1).getText()).toEqual('Finish migration to Testcafe'); 
  }) 
}) 

TestCafe

fixture('To do test') 
  .page('http://todo.org/'); 

test('Correct new items', async t => { 
  await t 
    .typeText('.todo-new-text', 'Start migration to Testcafe') 
    .click(Selector('button').withText('Add')) 
    .expect(Selector('.todo-list').childElementCount).eql(1) 
    .typeText('.todo-new-text', 'Finish migration to Testcafe') 
    .click(Selector('button').withText('Add')) 
    .expect(Selector('.todo-list').childElementCount).eql(2) 
    .expect(Selector('.todo-list').child(0).innerText).eql('Start migration to Testcafe') 
    .expect(Selector('.todo-list').child(1).innerText).eql('Finish migration to Testcafe') 
});

Install and Setup TestCafe

  1. Install TestCafe in your project directory. The --save-dev option adds TestCafe to the development dependencies.

    npm install --save-dev testcafe 
    
  2. Install a custom Angular builder for TestCafe. This command serves an Angular application and runs TestCafe tests.

    npm install --save-dev angular-testcafe 
    
  3. Optional. TestCafe supports standard CSS selectors, Selector objects, and framework-specific Selector objects.

    Install the testcafe-angular-selectors plugin to search for DOM elements in Angular applications.

    npm install --save-dev testcafe-angular-selectors 
    
  4. Configure the angular.json file. The devServerTarget option specifies a project against which to run tests. If this option is not specified, run the required project manually before a test run.

    Refer to the TestCafe configuration file topic and the schema.json file for the full list of options.

    { 
      "projects": { 
        "your_application_name": { 
          "architect": { 
            "e2e": { 
              "builder": "angular-testcafe:testcafe", 
              "options": { 
                "devServerTarget": "your_application_name:serve", 
                "browsers": [ //browsers array 
                  "chrome --no-sandbox" 
                ], 
                "src": ["e2e/*.e2e-spec.ts"] // path to tests 
              } 
            } 
          } 
        } 
      } 
    } 
    

Test Structure

TestCafe uses the following main objects to create tests:

The TestController object exposes test API methods. Use the test controller (as t) to call test actions, handle browser dialogs, use the wait function, or execute assertions. Use the async/await construction with the test controller to wait for called actions to complete. All test controller methods are chainable and asynchronous.

test ('My first test', async t => {
    await t
        .click('#signin')
        .typeTest('#login','login')
        .typeTest('#password','password')
        // your code
});

TestCafe supports standard CSS selectors and has its own Selector object to identify a target page element. TestCafe Selector objects have more extended API compared to standard CSS selectors. TestCafe selectors support a built-in automatic wait mechanism and do not require dedicated API to wait for redirects or page elements to appear.

TestCafe automatically converts a standard CSS selector within test controller methods into a TestCafe Selector object when a test runs.

import { Selector } from 'testcafe';   

fixture `Getting Started`   
 .page `http://devexpress.github.io/testcafe/example`;   

test('My first test', async t => {   
  await t 
    // Standard CSS selector. 
    // TestCafe will automatically convert this CSS selector to the TestCafe Selector object.
    .click('div') 
    // TestCafe Selector
    .typeText(Selector('div').withText('some text'))
}); 

The Selector object does not save its value. The value is re-evaluated for each element on a page. You can create the Selector object at runtime or save its value to a variable.

fixture `Getting Started`   
 .page `http://devexpress.github.io/testcafe/example`;   

// Save selector
const sel = Selector('div').child();

test('My first test', async t => {   
    await t
        .expect(Selector('#elementId').innerText).eql('text', 'check element text');
}); 

You can use a single Selector function or chain them to traverse through a DOM tree.

fixture `Getting Started`   
 .page `http://devexpress.github.io/testcafe/example`;   

const label = Selector('#tried-section').child('label');\

test('My first test', async t => {   
  await t 
    // your code
}); 

Get DOM Elements

  • Get an element with a specified tag name.

    // Protractor
    element(by.tagName('body'); 
    
    // TestCafe
    Selector('body'); 
    
  • Get an element with a specified id attribute.

    // Protractor
    element(by.id('id')); 
    
    // TestCafe
    Selector('#id');
    
  • Find an element by a specified CSS selector.

    // Protractor
    element(by.css('.class')); 
    
    // TestCafe
    Selector('.class'); 
    
  • Find an element with a specified input name attribute.

    // Protractor
    element(by.name('name')); 
    
    // TestCafe
    Selector (input[name="name"]); 
    
  • Find an element with a specified text in the CSS class name.

    // Protractor
    element(by.cssContainingText('.class', 'text')); 
    
    // TestCafe
    Selector('.class').withText('text'); 
    
  • Find the first link with the specified text.

    // Protractor
    element(by.linkText('text')); 
    
    // TestCafe
    Selector('link').withText('text'); 
    
  • Find an element with a specified ng-model expression (for AngularJS).

    // Protractor
    element(by.model('name')); 
    
    // TestCafe
    AngularJSSelector.byModel('name'); 
    
  • Find an element with specified text binding (for AngularJS).

    // Protractor
    element(by.binding('bindingname')); 
    
    // TestCafe
    AngularJSSelector.byBinding('bindingname'); 
    
  • Find an element with specified options (for AngularJS).

    // Protractor
    element(by.options('options')); 
    
    // TestCafe
    AngularJSSelector.byOptions('options'); 
    
  • Find an element with a specified ng-options expression (for AngularJS).

    // Protractor
    element(by.repeater('repeater');
    
    // TestCafe
    AngularJSSelector.byRepeater('repeater'); 
    
  • Find all elements with a specified condition.

    TestCafe’s Selector object automatically finds all elements that match the specified condition. Use an index notation to access elements in the array (.nth(index)).

    // Protractor
    element.all(by.css('.class'));
    
    // TestCafe
    Selector('.class');
    

    TestCafe uses the first element in the array if you pass this array to assertions.

See Also: Select Page Elements

Interact with DOM Elements

TestCafe requires that you use the TestController to interact with DOM elements.

Actions

TestCafe’s TestController object includes actions as its methods.

  • Execute a click.

    // Protractor
    element(by.css('.class')).click(); 
    
    // TestCafe
    Selector('.class').click();
    
  • Press a key.

    // Protractor
    element(by.css('.class')).sendKeys(protractor.Key.SPACE); 
    
    // TestCafe
    Selector('.class').pressKey('space');
    
  • Navigate to an URL.

    // Protractor
    browser.get('https://github.com/DevExpress/testcafe');
    
    // TestCafe
    .navigateTo('https://github.com/DevExpress/testcafe');
    
  • Type text.

    // Protractor
    element(by.css('.login')).sendKeys('login'); 
    
    // TestCafe
    .typeText('#login','login');
    
  • Hover an element.

    // Protractor
    browser.actions().mouseMove(element(by.css('.popover'))).perform();
    
    // TestCafe
    .hover('.popover');
    
  • Switch to an iframe.

    // Protractor
    browser.switchTo().frame(element(by.css('.demo-frame')).getWebElement());
    
    // TestCafe
    .switchToIframe('.demo-frame');
    
  • Wait before an action.

    // Protractor
    browser.wait(condition, 1000);
    
    // TestCafe
    .wait(1000);
    

See Also: All TestCafe actions

Assertions

TestCafe has a set of predefined assertions and supports third-party assertion libraries as npm dependencies.

TestCafe uses the built-in wait mechanism and recalculates the actual value until it matches the expected value or an assertion timeout expires. To execute an assertion, call the t.expect method followed by an assertion method with parameters.

  • Asserts that the actual value equals the expected value (the eql assertion).

    // Protractor
    expect({ a: 'bar' }).toEqual({ a: 'bar' }); 
    
    // TestCafe
    await t .expect({ a: 'bar' }).eql({ a: 'bar' }) 
    
  • Asserts that the actual value is true (the ok assertion).

    // Protractor
    expect('ok').toBeTruthy(); 
    
    // TestCafe
    await t .expect('ok').ok() 
    
  • Asserts that the actual value contains the expected value (the contains assertion).

    // Protractor
    expect('foo bar').toContain('bar');
    
    // TestCafe
    await t .expect('foo bar').contains('bar') 
    
  • Asserts that the actual type is the expected type (the typeOf assertion).

    // Protractor
    expect(typeof { a: 'bar' }).toBe('object'); 
    
    // TestCafe
    await t .expect({ a: 'bar' }).typeOf('object')
    

See Also: All TestCafe Assertions

Hooks

TestCafe can run reusable code sequences before or after tests and fixtures: Test hooks and Fixture hooks. Define the “before” and “after” hooks globally in the configuration file or in an individual test or fixture.

// Protractor
describe('hooks', function() { 
    before(function() { 
        // runs once before the first test in this block 
    }); 

    after(function() { 
        // runs once after the last test in this block 
    }); 

    beforeEach(function() { 
        // runs before each test in this block 
    }); 

    afterEach(function() { 
    // runs after each test in this block 
    }); 
    // test cases 
});    

// TestCafe
fixture`Hooks` 
    .before(() => { 
        // runs once before the first test in this fixture 
    }) 
    .after(() => { 
        // runs once after the last test in this fixture 
    }) 
    .beforeEach(() => { 
        // runs before each test in this fixture 
    }) 
    .afterEach(() => { 
        // runs after each test in this fixture 
    }); 

test 
    .before(() => { 
        // runs before each test 
    }) 
    .after(() => { 
        // runs after each test 
    }) 
    ('test', () => { 
        // test cases 
    }) 

See Also:

Timeouts, Screenshots and Other

TestCafe documentation helps you learn more details about the API, FAQ, common concepts, step-by-step guides and best practices. These resources allow you to migrate Protractor tests to TestCafe as easily as possible.

Run Tests

To run tests, execute the following CLI command. The command below starts a new browser window with a clean profile (for example, without extensions or profile settings). In this case, TestCafe ignores custom settings and runs more stable tests.

ng e2e 

To run tests in parallel, use the -c flag and specify which number of browser instances to invoke.

testcafe -c 3 chrome tests/sample-fixture.js 

See Also: Run Tests Concurrently

Debug Tests

You can pause tests to examine a web page and troubleshoot errors.

The --speed flag specifies the test execution speed.

testcafe chrome ./my-tests --speed 0.1 

Use any of the following test controller methods and CLI flags to switch a test to debug mode:

  • --debug-mode - Pauses test execution before the first action or assertion. So that, you can invoke the developer tools and then debug.

  • --debug-on-fail - Enters debug mode when a test fails.

  • t.debug - Pauses the test and allows you to use the browser’s developer tools.

fixture `Debugger example` 
 .page `http://devexpress.github.io/testcafe/example/`; 

test('Debugger', async t => { 
 await t 
   .debug(); 
});