How to simulate Meteor clients?

I’d like to simulate 50 meteor connections with users logging in, subscribing, and calling methods. I suppose I could spin up 50 meteor servers on different ports and use DDP.connect, DDP.subscribe, and DDP.call, but I’m not sure this is an accurate representation of 50 clients. How would user login work?

It would be great is some package or set of tools to do this. Any ideas?

1 Like

Great set to do such benchmarking or testing is phantomjs plus casperjs. You may very easy write some typical flows and run them on shell. If you do a bit randomizing, you have nearly user usage.

Very small starter: http://docs.casperjs.org/en/latest/quickstart.html#a-minimal-scraping-script

Hmm. I suppose I could use PhantomJS. Casperjs seems like overkill. I’m not building end-to-end tests or anything.

I still advise you to use casper, it makes it much easier to write flows than with pure phantomjs. Learning curve is absolute low to just get some automated benchmark scripts. Just drop all “tests” funtions and use .wait(ramdom) and .then() to create some typical “user” behaviors.

overkill how? it simulates a user, right? are your programming problems so trivial that you don’t deserve a convenient library to solve them?

Hmm. How can I access the Meteor namespace?

I’m just tried PhantomJS to connect to my Meteor app:

console.log('Loading a web page');
var page = require('webpage').create();
var url = 'http://localhost:3000/';
page.open(url, function (status) {
  //Page is loaded!
  phantom.exit();
});

I’m getting all kinds of errors though. No errors in Chrome, Safari, or Firefox though…

❯❯❯ phantomjs client.js                                                                                                                                                   
Loading a web page
TypeError: 'undefined' is not a function (evaluating 'ReactElementValidator.createElement.bind')

  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:11008
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:7691 in createDOMFactory
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:20622 in mapObject
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:7835
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41 in s
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:4428
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41 in s
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:12231
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41 in s
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:3948
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41 in s
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:62
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41 in s
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41 in e
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:21610
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:41
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:21611
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:21614
  http://localhost:3000/packages/grove_react.js?ab44d041c618ba865114410f4bf28961e2709d92:21798
TypeError: 'undefined' is not an object (evaluating 'Package['grove:react'].ReactiveMixin')

  http://localhost:3000/packages/ccorcos_react-meteor.js?36c89113e3a515dfa53c8b47ed079de18aad784e:22
  http://localhost:3000/packages/ccorcos_react-meteor.js?36c89113e3a515dfa53c8b47ed079de18aad784e:305
TypeError: 'undefined' is not an object (evaluating 'Package['grove:react'].ReactiveMixin')

  http://localhost:3000/packages/global-imports.js?ae4f93ebbfbb8faf2a0e65d09fe6b894f10f17db:4
ReferenceError: Can't find variable: lodash

  http://localhost:3000/lib/00init00.coffee.js?5694fd2f5e551094a875265e7102969348295784:2
  http://localhost:3000/lib/00init00.coffee.js?5694fd2f5e551094a875265e7102969348295784:17
ReferenceError: Can't find variable: Comments

  http://localhost:3000/lib/collections.coffee.js?d8c5fea3489c4380e1d5bc2fb05bcfff567dc1d9:2
  http://localhost:3000/lib/collections.coffee.js?d8c5fea3489c4380e1d5bc2fb05bcfff567dc1d9:110
ReferenceError: Can't find variable: Meteor

  http://localhost:3000/lib/methods.coffee.js?fc406fa41047a474af52b6a5b8e399e2b36c7225:2
  http://localhost:3000/lib/methods.coffee.js?fc406fa41047a474af52b6a5b8e399e2b36c7225:10
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/components/Error.coffee.js?6337909738a1c76f295062ed25025cf72ba024bb:4
  http://localhost:3000/client/views/components/Error.coffee.js?6337909738a1c76f295062ed25025cf72ba024bb:16
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/components/EventItem.coffee.js?f7eeafd3542b3c81ccab9306be27ae69811ebf59:4
  http://localhost:3000/client/views/components/EventItem.coffee.js?f7eeafd3542b3c81ccab9306be27ae69811ebf59:84
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/components/Scroller.coffee.js?8b0104ffe109e5b17ad4226b2e3ddfd345bb5776:2
  http://localhost:3000/client/views/components/Scroller.coffee.js?8b0104ffe109e5b17ad4226b2e3ddfd345bb5776:73
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/components/Spinner.coffee.js?4a7150d07efcd4f04b3cac0d0c17ccb396d9f783:4
  http://localhost:3000/client/views/components/Spinner.coffee.js?4a7150d07efcd4f04b3cac0d0c17ccb396d9f783:26
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/layout/Body.coffee.js?ccaedf66fa20ac2e69a4fea7de46bf06552e2845:4
  http://localhost:3000/client/views/layout/Body.coffee.js?ccaedf66fa20ac2e69a4fea7de46bf06552e2845:16
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/layout/Header.coffee.js?40fe266eff3eeb52eaf22f19f9e843d1061c8573:4
  http://localhost:3000/client/views/layout/Header.coffee.js?40fe266eff3eeb52eaf22f19f9e843d1061c8573:38
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/Explore.coffee.js?2990add72b5fbc8c1aad16fdcbf95448c9983213:4
  http://localhost:3000/client/views/Explore.coffee.js?2990add72b5fbc8c1aad16fdcbf95448c9983213:19
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/Feed.coffee.js?59747a98def85f5c82ecf3591b061f5b798a0c3d:4
  http://localhost:3000/client/views/Feed.coffee.js?59747a98def85f5c82ecf3591b061f5b798a0c3d:45
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/Plus.coffee.js?94338c2855fbbcb3397673abea0311dfc1c2fe25:4
  http://localhost:3000/client/views/Plus.coffee.js?94338c2855fbbcb3397673abea0311dfc1c2fe25:19
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/Profile.coffee.js?7fd8ca9cae4b08ce434a1023fb2698abc6141738:4
  http://localhost:3000/client/views/Profile.coffee.js?7fd8ca9cae4b08ce434a1023fb2698abc6141738:19
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/Search.coffee.js?f2ad6a5b444433cfbe2e04d48182e598c7e76a07:4
  http://localhost:3000/client/views/Search.coffee.js?f2ad6a5b444433cfbe2e04d48182e598c7e76a07:19
ReferenceError: Can't find variable: React

  http://localhost:3000/client/views/Welcome.coffee.js?8b67cc1c8db8c8d4db3f36e3ff9446fe8398b496:4
  http://localhost:3000/client/views/Welcome.coffee.js?8b67cc1c8db8c8d4db3f36e3ff9446fe8398b496:332
ReferenceError: Can't find variable: _

  http://localhost:3000/client/router.coffee.js?caad00d1dc4f6c749c7cd0056c45884dec70c440:26
  http://localhost:3000/client/router.coffee.js?caad00d1dc4f6c749c7cd0056c45884dec70c440:172

then drive a firefox or chrome in xvfb.

I’m just saying the simpler the solution, the better.

I will make you a short example

1 Like

That’s where you need Meteor Down: https://github.com/meteorhacks/meteor-down

4 Likes

All I want to do is set some random intervals for users logging in, creating subscriptions, calling methods, and logging out.

Here’s a simple idea for what I want to accomplish for one user.

newPost = ->
  Meteor.call 'newPost', faker.lorem.sentence()

subscriptions =  
  feed: ->
    Meteor.subscribe 'feed'
  search: ->
    Meteor.subscribe 'search', faker.characters(faker.random(1,10))
  detail: ->
    Meteor.subscribe 'post', _.sample(Posts.find().fetch())?._id

sub = null
randomSubscribe = ->
  sub?.stop?()
  sub = subscribe[_.sample(_.keys(subscriptions))]()

Meteor.loginWithPassword 'username', 'password', (error) ->
  Meteor.setInterval newPost, 5000
  Meteor.setInterval randomSubscribe, 2000

I suppose the benefit of Casperjs is so I can create all these subscriptions, and method calls etc. using the UI. That would be pretty sweet actually. @tomfreudenberg, what if I created some set of actions from each page and gave a probability of each action with different intervals – then I could just do a random walk around the webpage, clicking buttons, submitting forms etc. Maybe I can call the script with some number of clients, watch a bunch of printouts, and close all connected when I ctrl-c the process.

Here you are :slight_smile:

The original content is at my gist: https://gist.github.com/TomFreudenberg/994f107a029d152049eb

Hope you like it.


This is a very simple casperjs example, to show how to simulate a user accessing a meteor application like the todos example.

The casper script will do:

  1. connect to the todos example app
  2. add a new list
  3. iterate through all existing lists and open/load each once

Between each step there is a small random time, so that the “loads” are not aligned.

You can also enable casper debug to see a lot more debugging output that is very usefull for usability checks.

If you open your browser in parallel you will notice the growing number of the lists.

This was made and tested on MAC OSX with node@0.10.38, npm@1.4.28 and meteor@1.1.0.2.

First create a simple new example to use this demo script.

I expect, that the meteor server will run on default URL (localhost:3000)

# create a new meteor example

cd /tmp
meteor create --example todos
cd todos
meteor

Open an additional terminal to run the casper “user”

# install npms on first time
 
npm -g install casperjs
npm -g install phantomjs
npm -g install sugar

# get the casper script

cd /tmp
curl -L -O https://gist.githubusercontent.com/TomFreudenberg/994f107a029d152049eb/raw/48216827c9ab8e4170a404ed348930898ffe3afc/simple-actions-on-todos.casper.js

# run the magic

casperjs simple-actions-on-todos.casper.js

If you want to start a number of simulation processes in parallel, you can use bash technic like:

for ((i=1; i<=5; i++)) do casperjs simple-actions-on-todos.casper.js & done


Remark: I have added a number of requires that are not necessary on this casper script but I advise you to use them, cause those will help on more complex scripts.


  1. Casperjs module documentation
  2. Casperjs hompage
  3. Phantomjs homepage
  4. Sugarjs Features
2 Likes

Just updated my previous post.

This looks great! I’m definitely going to use this.

Is is possible to create a script that doesn’t end until I stop it with ctrl-c? Perhaps I can just call a recursive function within casper.then?

I’d also like to handle multiple users logging in and out randomly/dynamically in JS. Rather than use bash is it possible to do this in Javascript? Perhaps calling casper.start more than once?

If I may give you the advise, I would not create too complex scripts. We have good experiences also for testing and quality checks, when creating a number of seperated scripts - each with a defined usecase.

If you at the end use a bash script (as we do) or write some simple javascript childprocess umbrella, that depends on your personal preference. But with such an umbrella starter (or also a number of sets) you are much more flexible to produce random load on your app.

This is like we do.

I see. Thanks for the help! I’ll let you know how it goes…

So are you saying not to let a client script run indefinitely?

I think it is not possible but you can add a surrounding eachThen loop and use great sugarjs function :smile:

casper.eachThen((1).upto(100), function(response){

  // your complete suite loop

});

You have to recognize, that inside each level of inner functions() the this object pointer is your friend.

casper.then(function(){
  this.then(function(){
    this.then(function(){
      ...

with that you can create stepwise procedures with ease

Do NOT use casper. in inner functions. Only the outest should use the casper object.

IMHO infinitely should be a NO-GO

It would be nice to see things changing and working as I’m building…