Paper.js: Mocha unit test failing at creating a canvas

Created on 22 Sep 2016  路  9Comments  路  Source: paperjs/paper.js

I'm working on a redux project where we're using paper.js for some drawing features. We're testing our code in docker containers with mocha (unit tests) and nightwatch (integration tests, incl. selenium server). For installing dependencies we're using npm (npm install paper).

All tests are working on my machine. If I'm pushing the code to my repository (BitBucket) the Continuous Integration tool (Bamboo) starts building the docker containers and runs the tests. Then i'll get the following error:

Console output (Bamboo log):

Error: Canvas [object HTMLCanvasElement] is unable toprovide a 2D context.
    at Object.CanvasProvider.getCanvas (/home/nexboard/Nexboard/node_modules/paper/dist/paper-full.js:12919:10)
    at Object.CanvasProvider.getContext (/home/nexboard/Nexboard/node_modules/paper/dist/paper-full.js:12934:21)
    at new <anonymous> (/home/nexboard/Nexboard/node_modules/paper/dist/paper-full.js:13133:27)
    at Object.<anonymous> (/home/nexboard/Nexboard/node_modules/paper/dist/paper-full.js:12947:17)
    at Object.<anonymous> (/home/nexboard/Nexboard/node_modules/paper/dist/paper-full.js:14623:3)
    at Module._compile (module.js:541:32)
    at Module.replacementCompile (/home/nexboard/node_modules/nyc/node_modules/append-transform/index.js:63:13)
    at module.exports (/home/nexboard/node_modules/nyc/node_modules/default-require-extensions/js.js:8:9)
    at /home/nexboard/node_modules/nyc/node_modules/append-transform/index.js:67:4
    at require.extensions.(anonymous function) (/home/nexboard/node_modules/babel-register/lib/node.js:154:7)
    at /home/nexboard/node_modules/nyc/node_modules/append-transform/index.js:67:4
    at require.extensions.(anonymous function) (/home/nexboard/Nexboard/node_modules/babel-register/lib/node.js:154:7)
    at Object.<anonymous> (/home/nexboard/node_modules/nyc/node_modules/append-transform/index.js:67:4)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Module.require (module.js:468:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/nexboard/Nexboard/src/js/whiteboard/canvas.jsx:1:13)
    at ...

When mocha runs my tests and creates a main object, it fails requiring paper.js. I don't think it's because of my docker compose configuration so I'm leaving that one out. The code which is failing in paper.js are the following lines:

if (this.canvases.length) {
    canvas = this.canvases.pop();
} else {
    // This get's called (at the first time)
    canvas = document.createElement('canvas');
    clear = false;
}
var ctx = canvas.getContext('2d');
if (!ctx) {
    throw new Error('Canvas ' + canvas +
            ' is unable toprovide a 2D context.');
}

Because the integration tests are working I have the following thesis:
The problem could be that you need a DOM to create a canvas context but I'm not sure how this stuff is working - especially in a testing environment. What did you do to make your unit tests be successfully?

The regarding code if you like to keep a look:
Main.jsx:

import React from 'react';
import Toolbar from '../userinterface/Toolbar/Toolbar.jsx';
import Canvas from '../whiteboard/canvas.jsx';

export function Main() {
  return (
    <main className="main">
      <div id="content">
        <Toolbar />
      </div>
      <div id="background"></div>
      <Canvas />
    </main>
  );
}


export default Main;

canvas.jsx

import React from 'react';
import paper from 'paper';

var Canvas = React.createClass({

  componentDidMount: () => {
    console.log("Canvas did mount")
    console.log(paper)

    paper.setup('whiteboard');
    var path = new paper.Path();
        path.strokeColor = 'black';
    var start = new paper.Point(100, 100);
        path.moveTo(start);
        path.lineTo(start.add([ 200, -50 ]));
        paper.view.draw();

  },

  render: () => {
    return (
      <canvas id="whiteboard">
      </canvas>
    );
  }
})

export default Canvas

Thanks for having a look at my problem.

Kind regards,
Thomas

Most helpful comment

Great! Glad to hear you found the culprit. I shall add pkg-config to the README also. I think optional dependencies are installed by default, but don't cause an error if they fail. And if you don't want them, you choose --no-optional.

All 9 comments

What container are these tests run in? PhantomJS? Node? An actual browser?

I just figured out that I'm also having this problem on my machine. So it's possible that this is an error produced by using a docker container.

@lehni: I started my project with a redux template from a friend of mine. So it might be that i'm just doing some thing wrong :)
It's seems that I'm using jsdom for mocha. Before executing tests the following lines are executed:

require('babel-register')();

var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
  if (typeof global[property] === 'undefined') {
    exposedProperties.push(property);
    global[property] = document.defaultView[property];
  }
});

global.navigator = {
  userAgent: 'node.js'
};

Maybe this is jsdom bug. I'll have a look into this tomorrow.

It's crucial to know what container these tests are running in, so I'm asking again: Is it Node.js or a browser? Something in between?

If it does run in Node.js, then the exports into global that you quote above will probably make Paper.js falsely think that it's running in a browser environment. Tinkering with globals is bad practice, and JSDOM isn't a full browser replacement, so the mistake here I'd say is on the side of whatever package provides this code.

But it may also be that JSDOM is not able to create a canvas element because you don't have node-canvas installed (defined in optionalDependencies). Try that.

I'm using Node.js. In a node console on my mac machine I can run the following code require('jsdom').jsdom('').createElement('canvas').getContext('2d')
and it will return a canvas context.

When i connect to my docker container and run the same command in the console it returns null.

My docker container is based on alpine-node (a minimal linux distribution).

Can you inspect the log file of the process of installing the dependencies on docker? It probably fails to compile node-canvas because of unmet dependencies. Here what I tell travis CI to install:

- libcairo2-dev
- libpango1.0-dev
- libssl-dev
- libjpeg62-dev
- libgif-dev

In my logs I found the following error installing node-canvas:

> [email protected] install /home/nexboard/node_modules/canvas
    > node-gyp rebuild

  /bin/sh: ./util/has_lib.sh: not found
  gyp: Call to './util/has_lib.sh freetype' returned exit status 127 while in binding.gyp. while trying to load binding.gyp
  gyp ERR! configure error 
  gyp ERR! stack Error: `gyp` failed with exit code: 1
  gyp ERR! stack     at ChildProcess.onCpExit (/usr/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:305:16)
  gyp ERR! stack     at emitTwo (events.js:106:13)
  gyp ERR! stack     at ChildProcess.emit (events.js:191:7)
  gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:204:12)
  gyp ERR! System Linux 4.4.0-28-generic
  gyp ERR! command "/usr/bin/node" "/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
  gyp ERR! cwd /home/nexboard/node_modules/canvas
  gyp ERR! node -v v6.3.0
  gyp ERR! node-gyp -v v3.3.1
  gyp ERR! not ok 
  npm WARN install:[email protected] [email protected] install: `node-gyp rebuild`
  npm WARN install:[email protected] Exit status 1

This is a typical problem for using minimal linux distribution. That's why I switched to an trusty ubuntu image. Then I installed the libraries you suggested, but I still receive the same emtpy canvas context as before.

apt-get install -y libcairo2-dev libpango1.0-dev libssl-dev libjpeg62-dev libgif-dev
node
  > require('jsdom').jsdom('').createElement('canvas').getContext('2d')
  null

What did you mean with installing canvas? Do i have to connect jsdom and node-canvas manually?

EDIT:
After installing (only) node-canvas the following code snippet returns a context:
new (require('./node_modules/canvas/index.js'))(200, 200).getContext('2d')

But paper.js already installs jsdom and canvas on its own, am I right?

Okay I think I fixed this bug. I tried to install node-canvas on my docker machine but I ran into several problems with node-gyp. I fixed this after looking at hundreds of different github issues for node-canvas. Someone suggested to look at the node-canvas installation instructions :facepalm: . In the end I was missing "pkg-config". Here my installation command for node-canvas:
apt-get update && apt-get install -y libcairo2-dev libpango1.0-dev libssl-dev libjpeg62-dev libgif-dev pkg-config

I'll run the final tests over night and write back on monday if everything works fine.

PS: Do I have to install jsdom and canvas manually or can I just run "npm install paper --optional" or something like that?

Great! Glad to hear you found the culprit. I shall add pkg-config to the README also. I think optional dependencies are installed by default, but don't cause an error if they fail. And if you don't want them, you choose --no-optional.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

eugene-rozov picture eugene-rozov  路  6Comments

hapticdata picture hapticdata  路  5Comments

eljefedelrodeodeljefe picture eljefedelrodeodeljefe  路  5Comments

croqaz picture croqaz  路  6Comments

nicholaswmin picture nicholaswmin  路  3Comments