Search code examples
ruby-on-railsangularjsruby-on-rails-4.2ruby-2.3nodejs-8.11

angularjs nomod error only on rails feature specs using compiled assets


In one of our rails and angularjs recently upgraded angularjs from 1.2.9 to 1.3.0, replaced bower with yarn and went from node 0.8.28 to 8.11.1. Everything works fine with the application in the UI.

I fixed the missing module error in the app by turning strict dependency on using ng-strict-di directive and explicitly annotated all dependencies.

We used capybara and poltergiest for feature testing, I replaced poltergiest with selenium, all the jasmine specs are running fine.

We use grunt as a task runner and all assets are compiled into public folder in the root level and when capybara starts the server it runs the application from the public folder on specified port.

I don't understand or can't find how does capybara know to start the application from the compiled application in public folder.

For testing purposes I let the feature specs run in chrome and I get the infamous error,

[$injector:nomod] Module 'app' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.

I can't figure out why I don't get that error when I run the actual application but only in feature specs.

This is my Gruntfile.js

'use strict';
var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest;
var serveStatic = require('serve-static');

module.exports = function (grunt) {
  // load all grunt tasks
  require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

  // configurable paths
  var yeomanConfig = {
    app: 'app',
    dist: '../public'
  };

  try {
    yeomanConfig.app = require('./package.json').appPath || yeomanConfig.app;
  } catch (e) {}

  grunt.initConfig({
    ngtemplates: {
      app: {
        options: {
          base: 'app',
          // concat: '<%= yeoman.dist %>/scripts/scripts.js'
          concat: 'generated'
        },
        src: ['app/views/**/*.html'],
        dest: '.tmp/template.js'
      }
    },
    yeoman: yeomanConfig,
    watch: {
      all : {
        options : { livereload: true },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
          '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ],
        tasks: ['jshint']
      }
    },
    connect: {
      proxies: [{
        context: '/api',
        host: '127.0.0.1',
        port: 5000,
        https: false,
        changeOrigin: false
      }, {
        context: '/public/api',
        host: '127.0.0.1',
        port: 5000,
        https: false,
        changeOrigin: false
      }, {
        context: '/print',
        host: '127.0.0.1',
        port: 5000,
        https: false,
        changeOrigin: false
      }, {
        context: '/users',
        host: '127.0.0.1',
        port: 5000,
        https: false,
        changeOrigin: false
      }],
      livereload: {
        options: {
          port: 9000,
          // Change this to 'localhost' to restrict access to the server from outside.
          hostname: '0.0.0.0',
          middleware: function () {
            return [
              require('connect-livereload')(),
              proxySnippet,
              serveStatic('.tmp'),
              serveStatic('test'),
              serveStatic(yeomanConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function () {
            return [
              serveStatic('.tmp'),
              serveStatic('test')
            ];
          }
        }
      },
      teste2e: {
        options: {
          port: 9002,
          hostname: 'localhost',
          middleware: function () {
            return [
              proxySnippet,
              serveStatic('.tmp'),
              serveStatic('test'),
              serveStatic(yeomanConfig.app)
            ];
          }
        }
      }
    },
    open: {
      server: {
        url: 'http://localhost:<%= connect.livereload.options.port %>'
      }
    },
    clean: {
      options: {
        force: true
      },
      dist: ['.tmp', '<%= yeoman.dist %>/*'],
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        '<%= yeoman.app %>/scripts/{,*/}*.js',
        '<%= yeoman.app %>/modules/{,*/}*.js'
      ]
    },
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        singleRun: true
      },
      dev: {
        configFile: 'karma.conf.js',
        singleRun: false
      },
      e2e: {
        configFile: 'karma-e2e.conf.js',
        singleRun: true
      }
    },
    coffee: {
      dist: {
        files: {
          '.tmp/scripts/coffee.js': '<%= yeoman.app %>/scripts/*.coffee'
        }
      },
      test: {
        files: [{
          expand: true,
          cwd: '.tmp/spec',
          src: '*.coffee',
          dest: 'test/spec'
        }]
      }
    },
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: 'app/components',
        relativeAssets: true
      },
      dist: {},
      server: {
        options: {
          debugInfo: true
        }
      }
    },
    concat: {
      dist: {
        files: {
          '<%= yeoman.dist %>/scripts/scripts.js': [
            '.tmp/scripts/{,*/}*.js',
            '<%= yeoman.app %>/scripts/{,*/}*.js'
          ]
        }
      }
    },
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>'
      }
    },
    rev: {
      files: {
        src: ['<%= yeoman.dist %>/lib/components.js', '<%= yeoman.dist %>/lib/components-ie8.js', '<%= yeoman.dist %>/lib/components-ie.js', '<%= yeoman.dist %>/scripts/scripts.js', '<%= yeoman.dist %>/styles/main.css']
      }
    },
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        dirs: ['<%= yeoman.dist %>']
      }
    },
    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },
    cssmin: {
      dist: {
        files: {
          '<%= yeoman.dist %>/styles/main.css': [
            '.tmp/styles/{,*/}*.css',
            '<%= yeoman.app %>/styles/{,*/}*.css'
          ]
        }
      }
    },
    htmlmin: {
      dist: {
        options: { },
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>',
          src: ['*.html', 'views/*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>/scripts',
          src: '*.js',
          dest: '<%= yeoman.dist %>/scripts'
        }]
      }
    },
    uglify: {
      dist: {
        files: {
          '<%= yeoman.dist %>/scripts/scripts.js': ['<%= yeoman.dist %>/scripts/*.js']
        }
      }
    },
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,txt}',
            '.htaccess',
            'fonts/**/*',
            'scriptx/**/*',
            'lib/**/*',
            'images/{,*/}*.{gif,webp,svg}'
          ]
        }]
      }
    }
  });

  grunt.registerTask('server', [
    'clean:server',
    'coffee:dist',
    'compass:server',
    'configureProxies',
    'connect:livereload',
    'open',
    'watch'
  ]);

  grunt.registerTask('test', [
    'clean:server',
    'coffee',
    'compass',
    'connect:test',
    'karma:unit'
  ]);

  grunt.registerTask('test-e2e', [
    'clean:server',
    'coffee',
    'compass',
    'configureProxies',
    'connect:teste2e',
    'karma:e2e'
  ]);

  grunt.registerTask('deploy', [
    'clean:dist',
    'coffee',
    'compass:dist',
    'useminPrepare',
    'imagemin',
    'cssmin',
    'htmlmin',
    'ngtemplates',
    'concat',
    'ngmin',
    'uglify',
    'copy',
    'rev',
    'usemin'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'jshint',
        'karma:unit',
    'compass:dist',
    'useminPrepare',
    'imagemin',
    'cssmin',
    'htmlmin',
    'ngtemplates',
    'concat',
    'ngmin',
    'uglify',
    'copy',
    'rev',
    'usemin'
  ]);

  grunt.registerTask('default', ['build']);
};

This is my feature spec,

require 'spec_helper'
require 'vcr'
require 'timecop'

Capybara.default_max_wait_time = 10

feature "Consignment creation and dispatch"  do
  include SpecHelpers
  include Acceptance

  scenario "Creating a Consignment", :js => true do

    puts "Running with #{Capybara.current_driver}"

    VCR.use_cassette('cassete file') do
    end

  end

end

Let me know if you need more information.


Solution

  • I finally figured out what was happening,

    The way I started debugging was, I pulled the compiled assets from public folder in the app in jenkins and used that to test it in local and I could see it was missing some packages and the reason that was happening was (I am not sure how),

    In the old setup we were pushing components folder in the repo and it had the dist folder with scripts in it but now I'm downloading everything from package.json and it doesn't generate the dist folder on server but does on local, hence the missing module error.