Q:

opening angular app pages in sequence - second page.open callback doesn't get called

I am trying to open 2 angular app pages in sequence to get their screenshots using phantomjs. Page 1 needs to open before page 2 as it prepares some data for page 2. I am using two nested setTimeout() functions in the following way:
var page = require('webpage').create(),
  t, url;

phantom.addCookie({
  'name': 'token',
  'value': '<authentication-token-goes-here>',
  'domain': 'localhost'
});

t = Date.now();
url = "http://localhost:8000/#/page1";

page.onConsoleMessage = function(msg, lineNum, sourceId) {
  console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};

page.viewportSize = {
  width: 1366,
  height: 768
};

page.clipRect = {
  top: 0,
  left: 0,
  width: 1366,
  height: 768
};

page.open(url, function(status) {

  setTimeout(function() {

    console.log('page 1 status: ', status);
    page.render("page1.png");

    var url = "http://localhost:8000/#/page2";

    page.open(url, function(status) {
      console.log('page 2 status: ', status);
      setTimeout(function() {
        page.render("page2.png");
        phantom.exit();
      }, 5000);
    });
  }, 5000);
});

The first console statement: console.log('page 1 status: ', status); gets printed and I get the applications logs and screenshot of page 1 successfully but second console log (page 2 status) doesn't get printed, because the inner page.open()'s callback is not called. It also hangs the console itself, because phantom.exit() is not called due to callback not being called.
But even as the inner page.open() callback is not called, I can see the application logs of page 2 (like XHR response logs) printed successfully! Just that there is no activity after the last application log of page 2.
I tried this code with other websites (opened google and facebook in sequence), and it worked fine. But the same code is not working for my angular app. What could be the reason?

A:

Ok, I could replicate the issue, here is some code in AngularJS which uses routing:

var routingExample = angular.module('Example.Routing', []);
routingExample.controller('HomeController', function($scope) {});
routingExample.controller('BlogController', function($scope) {});

routingExample.config(function($routeProvider) {
  $routeProvider.
  when('/home', {
    templateUrl: 'home.html',
    controller: 'HomeController'
  }).
  when('/blog', {
    templateUrl: 'blog.html',
    controller: 'BlogController'
  }).
  otherwise({
    redirectTo: '/home'
  });
});
<html>

<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <meta name="robots" content="noindex, nofollow">
  <meta name="googlebot" content="noindex, nofollow">

  <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.min.js"></script>
  <link rel="stylesheet" type="text/css" href="/css/normalize.css">
  <link rel="stylesheet" type="text/css" href="/css/result-light.css">

  <title>AngularJS Routing</title>

  <script type="text/javascript" src="script.js"></script>
</head>

<body ng-app="Example.Routing" class="ng-scope">
  <script type="text/ng-template" id="home.html">
    <h1>Home</h1>
  </script>
  <script type="text/ng-template" id="blog.html">
    <h1>Blog</h1>
  </script>

  <div>
    AngularJS Routing

    <div>
      <a href="#/home">Home</a>
      <a href="#/blog">Blog</a>
    </div>

    <div ng-view></div>
  </div>
</body>

</html>

The router uses # in URL, it means that when you run page.open in phantomJS it won't force reload (similar to standard browser behavior). Therefor phantomJS has no async task to do and won't call any callbacks. One way, is to use second page object or force reload when you set the new URL up. However I imagine you don't want to do that. Here is what I did instead (that's my phantomJS script):

var page = require('webpage').create(),
  t, url;

phantom.addCookie({
  'name': 'token',
  'value': '<authentication-token-goes-here>',
  'domain': 'localhost'
});

t = Date.now();
url = 'http://localhost:8080/#/home';

page.onConsoleMessage = function(msg, lineNum, sourceId) {
  console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};

page.viewportSize = {
  width: 1366,
  height: 768
};

page.clipRect = {
  top: 0,
  left: 0,
  width: 1366,
  height: 768
};

page.open(url, function(status) {

  setTimeout(function() {

    console.log('page 1 status: ', status);
    page.render('page1.png');
    console.log('page1.png rendered');

    console.log('moving to the second page ' + url);
    page.onUrlChanged = function(targetUrl) {
      console.log('URL changed, New URL: ' + targetUrl);
      console.log(page.url);
      if (targetUrl !== 'http://localhost:8080/#/home') { // the default page
        setTimeout(function() {
          page.render('page2.png');
          phantom.exit();
        }, 1000);
      }
    };

    console.log('onUrlChanged callback handler set up');

    var url = 'http://localhost:8080/#/blog';
    console.log('opening the ' + url);
    page.open(url, function(status) {});

  }, 1000);
});

Screenshots:

