Puppeteer: NodeJS browser automation

Puppeteer is a Node library which provides a high-level API to control headless Chrome over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome.

Demo is woth thousand words. API Documentation covers all the aspects of browser emulation/handling I could think of..

Whether it will be a replacement of NightwatchJS or a sub-component in the current end-to-end stack – fun times of trials & errors will tell.

Some useful links for JS coding

Few nice things to use in close future:

Hoodie

A fast, simple and self-hosted backend as a service for your (web) apps, Open Source and free. No need to write server-side code or database schemas. Makes building offline-capable software a breeze.

GREENKEEPER.IO

Get safety & consistency with real-time monitoring and automatic upates for npm dependencies.

Reduce CSS bundle size by cutting class names

This is pure awesomeness! Gajus Kuizinas went beyond asset minifiers which resulted from 140KB to 53KB CSS size decrease.

There is one thing a minifier cannot do – change the selector names. This is because a CSS minifier does not control the HTML output. Meanwhile, CSS names can get long.

All the details are in the post worth reading, if still think that webpack can only merge JS files in a bundle.

JSData as ember-data alternative

Weekends are normally for RnD time, so this weekend’s topic – standardization of front/backend communication. Considering backend as API-only and fully offloading the rendering part to JavaScript frameworks, EmberJS had a really nice concept of ember-data that acted in between rendering components and backend objects received via API. Another alternative can be considered as JSData:

SData is a framework-agnostic, datastore-agnostic ORM (Object-Relational Mapper) for Node.js and the Browser.

Adapters allow JSData to connect to various data sources such as Firebase, MySql, RethinkDB, MongoDB, localStorage, Redis, a REST API, etc. With JSData you can re-use your data modeling code between environments, keep your data layer intact when transitioning between app frameworks, and work with a unified data API on the server and the client. JSData employs conventions for rapid development but allows for endless customization in order to meet your particular needs.

Considering the use of JSData with JSON-API standards, there’re couple of adapters that transpile JSData to JSON-API.

CakePHP with NightwatchJS on Travis CI

After the release of Headless Chrome browser (v59+) most of the automation tools slowly started reducing the usage of Selenium, by replacing it with the combination of chromedriver + chrome/chromium pre-installed browser (like Travis CI does on their images).

As a replacement, I’ve tried playing around with the combination of Karma/Chai to test JavaScript functionality of certain projects we have in Qobo, which went well, but it only introduced the unit test approach towards the code base. What I wanted was user automation, emulating browsers Page Object Model (POM). The choice fell on NightwatchJS library that works with headless Chrome and needs only minor tweaking.

Setting up NightwatchJS

I’ve used a combination of NightwatchJS with Mocha test framework, but Nightwatch itself has a good support of assertion methods to do some basic checks on the browser requests, but first things first.

We start with package.json for Yarn/NPM.

{
  "devDependencies": {
    "chromedriver": "^2.31.0",
    "mocha": "^3.5.0",
    "nightwatch": "^0.9.16"
  },
  "scripts": {
    "test": "./node_modules/nightwatch/bin/nightwatch"
  }
}

yarn install and proceeding with nightwatch configurations:

const chromedriver = require('chromedriver');

module.exports = {
  before: function(done) {
    chromedriver.start();

    done();
  },

  after: function(done) {
    chromedriver.stop();

    done();
  }
};

After we explained the library how to handle chromedriver, we set the configurations for nightwatch.json:

{
  "src_folders": ["tests/Frontend"], 
  "output_folder": "build/coverage/",
  "custom_commands_path": "",
  "custom_assertions_path": "",
  "page_objects_path": "",
  "globals_path": "globals.js",
  "selenium": {
    "start_process": false
  },
  "test_runner": {
    "type": "mocha",
    "options": {
        "ui": "bdd",
        "reporter": "list"
    }
  },
  "test_settings": {
    "default": {
      "selenium_port": 9515,
      "selenium_host": "localhost",
      "default_path_prefix" : "",

      "desiredCapabilities": {
        "browserName": "chrome",
        "chromeOptions" : {
          "args" : ["--no-sandbox", "--headless", "--disable-gpu"]
        },
        "acceptSslCerts": true
      }
    }
  }
}

NightwatchJS still uses selenium_` naming options in the configurations (for backward compatibility reasons), so don’t get scared by the naming.

And the final touch, testing login action:

describe('Testing login UsersController::login() method', () => {
    var loginUrl = 'http://localhost:8000/login';

    before(function(browser, done) {
      done();
    });

    after(function(browser, done) {
      browser.end(function() {
        done();
      });
    });

    afterEach(function(browser, done) {
      done();
    });

    beforeEach(function(browser, done) {
      done();
    });

    it('gets [login] page', (browser) => {

        browser
            .url(loginUrl)
            .waitForElementVisible('.login-box').present;
    });

    it('trying to [login]', (browser) => {
        browser
            .url(loginUrl)
            .waitForElementVisible('.login-box', 2000)
            .assert.elementPresent('input#username')
            .setValue('input#username', 'username')
            .assert.elementPresent('input#password')
            .setValue('input#password', 'password')
            .submitForm('form')
            .pause(2000)
            .assert.elementPresent('nav');
    });
});

Running yarn test you should get something like this:

running nightwatchjs with yarn

NightwatchJS on Travis CI

Last but not the least – configuring Travis CI to run NightwatchJS on a PHP distro image. The config is partially pasted from project-template-cakephp , (pull request #340 with NightWatchJS).

sudo: true
dist: trusty

language: php

php:
    - 5.6
    - 7.0
    - 7.1
    - nightly

#enabling NodeJS
node_js:
  - "7"

#setting up stable Chrome, which has headless support 
#out of the box
addons:
  chrome: stable

#caching Yarn and its node_modules
cache:
  yarn: true
  directories:
    - node_modules

# installing JS packages and starting up the server
before_script:
    - yarn install
    - ./bin/phpserv >/dev/null 2>&1 &
    - sleep 5

# once you're done with PHPUnit/PHPCS,
# it's time to check the UI with `yarn test`
script:
  - ./vendor/bin/phpunit --group example
  - ./vendor/bin/phpunit --exclude-group example
  - ./vendor/bin/phpcs
  - yarn test

 

EmberJS: JCF with components

JavaScript Custom Form elements is a useful jQuery plugin for customising your form elements, in case you have to get away from the default styling of the form elements. However, there’s a tiny “but” with the plugin when you use it with EmberJS. JCF initially designed to be used on the global scope, and in some case (like mine), it’s not what you need.

If you’re using custom select-element with JCF without JCF.Scrollable, the list becomes unusable in few cases:

  1. It overflows the layout of the site
  2. Not keyboard-friendly, when you try to filter the options and not scroll till the end.
  3. When you use Ember addons like emberx-select, it doesn’t like custom data-attributes.

Eventually, using components concept of Ember, it’s easier to isolate the setups of JCF.

In my case, language options have only few options,where I prefer to wrap the native select options:

import Ember from 'ember';

export default Ember.Component.extend({
  cart: Ember.inject.service('shopping-cart'),

  didRender() {
    Ember.run.scheduleOnce('afterRender', function(){
      //#currency-options is the action <select>-element
      jcf.replace('#currency-options','Select', {
        "wrapNative": false,
        "wrapNativeOnMobile": true
      });
    });
  },

And, to avoid custom wrapping of dropdowns, for instance, country list, better to initiate JCF like this:

import Ember from 'ember';

export default Ember.Component.extend({
  classNames: ['input2','country-list'],
  didRender() {
    Ember.run.scheduleOnce('afterRender', function(){
      jcf.replace('#input2-select','Select');
    })
  }
});

Few more samples of the code, could be found in gist.