28

My app requires complex steps within the UX to reach some of its many states. This is making the dev/test cycle extremely cumbersome for simple layout changes which have to be visually verified for a wide range of states.

So, I'm looking into the practicality of taking a dump/snapshot of the running app (ie. window.angular, or perhaps $rootscope) in such a way that I can quickly restore from that snapshot, run a $rootscope.$digest() and et voila.

Any suggestions on how this can be achieved?

I'm not looking for a restored snapshot to be functional, eg it needn't have active watchers or broadcast subscriptions. It just needs to faithfully render a view for visual inspection.

--edit--

I'm starting to think this can't be done.

What I have come to realise is that all of my Angular projects from now on will have a single service called VmStateService, and basically, every single item of VM data that affects the rendered view must live in this single service, which is injected into every controller. That way I only have a single, clean object (it has no functions), that I can dump to a string, or save to local storage, and restore to create any view that I want to test.

I guess it's unfortunate that everybody learns AngularJS by doing $scope.foo = "bar", and then spends the rest of their career realising what a mess that creates.

pinoyyid
  • 21,499
  • 14
  • 64
  • 115
  • Are the states using ui-router (stateProvider)? Any reason not to just refresh? Or are you just looking for a programmatic way to do it? – itamar Dec 18 '15 at 13:35
  • 2
    Can't you use the Selenium firefox exension to script the steps needed to reach your state? – Bogdan Dec 18 '15 at 13:38
  • Does your set of watchers change on any scope during the test? – Tamas Hegedus Dec 18 '15 at 14:52
  • @itamar that will take me to the initial state of the view/controller. I need a snapshot after certain activities (of which there are many), have taken place after the controller loaded. – pinoyyid Dec 18 '15 at 15:24
  • @Bogdan we're already using selenium. We're looking for something instant, and which testers can use to snapshot an application in the event of an error. – pinoyyid Dec 18 '15 at 15:25
  • @hege_hegedus we don't have many watchers, and it's not important that the app is necessarily functional after a snapshot is restored. Merely that its view data and hence DOM is the same as when the snapshot was taken. – pinoyyid Dec 18 '15 at 15:26
  • I have no idea of your project structure but I believe since you have too many states and `complex steps within the UX to reach some of its many states`, I believe you are using a overly complicated structure. Simpler testable methods involve: First: Creating child state (eg: ui-router) and sharing data from parent. Second: Use a single service to store data used across a state and its children; especially when you are using the same data across different other non-inherited states or isolated states/scope chain. Use controllerAs syntax where possible if you want to avoid $scope related issues – Gary Dec 22 '15 at 15:55
  • Its good to have states for most things that you feel can have states or functions that needs to run before every or after every state. But if you create simpler structures it will be more testable (snappable), rather than debugging heavily after every state throws an error or becomes non-debuggable and non-testable. This is what I have noticed. For unavoidable functions that need to run, put in services intelligently - not necessarily in one single service. More items in one service makes it heavy and memory intense. – Gary Dec 22 '15 at 15:58
  • `I'm looking into the practicality of taking a dump/snapshot of the running app (ie. window.angular, or perhaps $rootscope) in such a way that I can quickly restore from that snapshot` Can you explain why would you want to do this? For testing or for checking what is used/run or for something else? Good thing if we know what you are restoring, how, and what you are checking specifically. Else, you will have all conceptual answers. – Gary Dec 22 '15 at 16:06
  • The point that perhaps I need to make clearer, is that I'm not interested in a mechanism for testing logic. I already have enough coverage with Karma. What I'm missing is visually checking the aesthetics of the rendering under a range of data and navigation scenarios. – pinoyyid Dec 22 '15 at 16:52
  • Karma+Protractor+Selenium. Aesthetics - go manual. Whatever you do, whether checking the dom structure or anything Aesthetics is in the eye of the beholder not dom. – Gary Dec 23 '15 at 03:43
  • exactly. So I'm looking for a quick way to get something before the beholder's eye. Like I said, we already have karma and protractor in place and running well for testing. I now need to provide tools to support visual review. – pinoyyid Dec 23 '15 at 08:18

5 Answers5

2

If you do your testing with karma then you can use karma-fixtures-preprocessor.

The gist:

  1. create a service that returns a mock or create test fixtures
  2. using dependency injection (angular for the win); inject your mock / mock data and test your logic in isolation.

If you have problems with this approach; then you most likely have weak separation of concerns.

Check out these great resources:

my $0.02 analysis

My app requires complex steps within the UX to reach some of its many states. This is making the dev/test cycle extremely cumbersome for simple layout changes which have to be visually verified for a wide range of states.

This does sound a lot like weak separation of concerns. If you are given all the data; it should be easy to trigger a view, or go to a state.

So, I'm looking into the practicality of taking a dump/snapshot of the running app (ie. window.angular, or perhaps $rootscope) in such a way that I can quickly restore from that snapshot, run a $rootscope.$digest() and et voila.

This does sound a lot like using fixtures. As for what data you would need to record; using http interceptors to log the content could work for you (don't do that in prod). You could then use the content you just logged for use in your mock data.

testing a view with different inputs

That usually means you want to test custom directives with different data. You could do this with:

  • as many fixtures as needed for each testcase,
  • beforeEach to load the proper data for a group of testcases,
  • using $digest() to update the view,
  • checking the updated markup with .find() or .attr(), etc, on the element.
  • checking the rendering with .clientHeight, .clientWidth, etc.

validating (visually) many different UI elements

There is a lot you can do for manually checking elements.

Adding a "test page", where the same elements are shown with different data, classes, styles, etc. When some of the data is provided as a user input, and it is difficult to check in an automated fashion; this may be a valuable approach. Examples are: Bootswatch which lets you check many UI elements of bootstrap under different themes. jquery UI ThemeRoller which lets you quickly check many elements under different themes.

dump / restore functionality

This question has some good points about accessing child scopes from parent scope: AngularJS - Access to child scope

Additional points:

  • you need to know what the markup is before it gets updated,
  • dumping may be easy if you manage to get data from all the necessary scopes,
  • however doing the restore may not work at all due to how you would do the updates. e.g. doing one element at a time may trigger updates, not doing it in the proper order may trigger updates that may modify elements, etc.

This gets very hairy. I don't know a library that provides generic dump / restore functionality.

Community
  • 1
  • 1
dnozay
  • 23,846
  • 6
  • 82
  • 104
  • 1
    You're right. Like many apps that we end up inheriting "I wouldn't start from here". Just to clarify what I'm looking for, it's not the logic that I'm testing (got that covered with Karma), it's the rendering. Let's say I have a table, I need to be able to quickly see that table with 0, 1 a screenfull, more than a screenfull of rows. I need to see it with very short data strings, and also very long strings. I need to be able to see it in multiple languages. I also need to be able to see it in a variety of skins (we support skinning). So it's all 'bout the view, 'bout the view...view...view – pinoyyid Dec 21 '15 at 09:18
  • you don't need to "see" it to be able to test it; if you translate the expected output into proper html markup checks then you can automate the tests you are currently doing. – dnozay Dec 21 '15 at 16:25
  • it's more of a visual inspection to confirm the aesthetics and soft features like word wrap. It would take forever to try to encapsulate in test cases, what a human eye will spot in milliseconds – pinoyyid Dec 22 '15 at 03:09
  • How about building a test page that will have one directive for each testcase then? – dnozay Dec 22 '15 at 03:17
  • @pinoyyid, sounds to me like you need some unit tests on your directives, if you're interested in something that specific, and already have tests on your logic. You can achieve that with Karma / Jasmine / Mocha as well. – Bryan Rayner Dec 22 '15 at 16:09
  • Hi Bryan. You've missed the point of the question. I have Karma and Protractor providing logic testing already. I'm NOT looking for another testing tool. I'm looking for a tool to supplement that by allowing me to quickly restore to any given model state, so I can visually inspect the aesthetics of the rendered view. – pinoyyid Dec 25 '15 at 08:02
1

The issue with simply using JSON.Stringify is that it won't capture functions properly, nor will it capture legitimate undefined values or circular references. Something that you can use as a starting point is Node JS's util.inspect():

https://github.com/nodejs/node/blob/master/lib/util.js#L87

This already does half of the work - reads an object out, formats it properly, manages all nuances. Also reads functions and circular references. You have to do the other half of the work in translating it back, but you should be able to.

(And these are Node's license terms - MIT license for Node itself, other libraries might be slightly more strict (but not that much more)):

https://raw.githubusercontent.com/nodejs/node/master/LICENSE

TheHans255
  • 2,059
  • 1
  • 19
  • 36
1

Edit

First, if all you're interested in doing is getting a view for visual inspection, why not do this?

document.body.parentNode.innerHTML

But, you likely want more than that, especially if you want to capture the value of any input fields, etc, things that aren't represented by the static HTML of the DOM.

To solve your problem, you must capture whatever information you require, to re-create your view state. In Angular, this likely means:

  • The current URL
  • Any values on $rootScope
  • The internal state of any services which are supplying data to your view layer.

This is difficult to do unless you plan your application architecture around this feature. No matter how you slice it, you'll need to do some refactoring. You'll also need an architecture which encourages a state which you can snapshot, so that future development is also able to be saved/restored.

It sounds like you could benefit from the FLUX architecture. If you're unfamiliar with FLUX, I recommend you look at the flux website.

I have implemented a version of FLUX in my Enterprise Angular app, by using RxJS for actions, and services for the stores (I just name them <Feature>Store, and they're injected via module.service('<Feature>Store', /*Definition*/)).

However, there is an implementation of Flux which might solve what you're looking for, https://github.com/rackt/redux. Redux is built around React, so you'd need to build a bridge from the application state layer, to the view (which could be controlled by Angular).

Redux keeps the application state in a single JS Object, and tracks every atomic change to that object. You'd be able to get an instantaneous saving/loading of the application state, in theory.

Bryan Rayner
  • 4,172
  • 4
  • 26
  • 38
  • 1
    I'm not the one with the downvote, but I guess it's because you didn't answer the question. I didn't ask "any suggestions how I redesign my app", I asked "how do I snapshot my existing app". SO is very focussed on being a Q&A site, not a discussion forum. hth – pinoyyid Dec 22 '15 at 03:12
  • That seems valid - Seems that you ended up coming to a solution similar to FLUX, with your VmStateService. Best of luck. – Bryan Rayner Dec 22 '15 at 15:50
0

Here's a naive tentative.

Usage:

  • serialize($rootScope) to get a string, serialized version of the root scope and children that you'll save in a file, localStorage, etc.

  • restore(myCopy, $rootScope) to put everything back into the $rootScope. myCopy is the string, serialized version returned by the copy method earlier.

Only custom properties (and not functions) are serialized. Anything starting with a dollar sign is considered private to angular and shouldn't be messed with. If you happen to have custom properties that start with the $ sign you can adjust the function to only ignore the one private to angular.


function serialize(target, source) {
  source = source || {};
  for (var key in target) {
    if (!target.hasOwnProperty(key)) { continue; }
    if (key[0] === '$' || key === 'constructor') continue;

    if (typeof target[key] !== 'function') {
      try {
        source[key] = JSON.stringify( target[key] );
      } catch(e) {}
    }
  }

  if (target.$$nextSibling) {
    source.$$nextSibling = {};
    serialize(target.$$nextSibling, source.$$nextSibling);
  }

  if (target.$$childHead) {
    source.$$childHead = {};
    serialize(target.$$childHead, source.$$childHead);
  }

  return JSON.stringify(source);
}

function restore(copy, $rootScope) {
  try {
    copy = JSON.parse(copy);
  } catch(e) {}

  for (var key in copy) {
    if (!copy.hasOwnProperty(key)) { continue; }

    try {
      $rootScope[key] = JSON.parse(copy[key]);
    } catch(e) {
      $rootScope[key] = copy[key];
    }
  }

  if (copy.$$nextSibling) {
    $rootScope.$$nextSibling = $rootScope.$$nextSibling || {};
    restore(copy.$$nextSibling, $rootScope.$$nextSibling);
  }

  if (copy.$$childHead) {
    $rootScope.$$childHead = $rootScope.$$childHead || {};
    restore(copy.$$childHead, $rootScope.$$childHead);
  }
}

// Create a $rootScope serialized copy that can be stored in local storage, etc
var copy = serialize($rootScope);

// Restore a serialized copy into $rootScope
restore(copy, $rootScope);
floribon
  • 19,175
  • 5
  • 54
  • 66
  • thanks for the fragment. I think there are a lot of additional edge cases that need dealing with, eg. I do want some $xxx recursion, but not all, I need to deal with circular references and duplicate references. – pinoyyid Dec 20 '15 at 04:51
0

I see that my small project could satisfy your requirements partially.

https://github.com/vorachet/angular-state-manager

It is still young and the design goal is to be a maintenance tool to aid Angular application comprehension. I welcome to study your detailed requirements and try to continue the development to solve your problem.

vrj
  • 531
  • 3
  • 4