Skip to content

Commit

Permalink
Lint & prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre Haller committed Jan 23, 2020
1 parent 6bdd7e7 commit 67062da
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 209 deletions.
1 change: 1 addition & 0 deletions .eslintignore
@@ -1,2 +1,3 @@
node_modules
dist
package-lock.json
1 change: 1 addition & 0 deletions .prettierignore
@@ -1,2 +1,3 @@
node_modules
dist
package-lock.json
142 changes: 77 additions & 65 deletions README.md
Expand Up @@ -2,10 +2,9 @@

gremlins.js is a monkey testing library written in JavaScript, for Node.js and the browser. Use it to check the robustness of web applications by unleashing a horde of undisciplined gremlins.


> Kate: *What are they, Billy?*
> Kate: _What are they, Billy?_
>
> Billy Peltzer: *They're gremlins, Kate, just like Mr. Futterman said.*
> Billy Peltzer: _They're gremlins, Kate, just like Mr. Futterman said._
![TodoMVC attacked by gremlins](http://static.marmelab.com/todo.gif)

Expand All @@ -15,19 +14,19 @@ While developing an HTML5 application, did you anticipate uncommon user interact

Gremlins.js simulates random user actions: gremlins click anywhere in the window, enter random data in forms, or move the mouse over elements that don't expect it. Their goal: triggering JavaScript errors, or making the application fail. If gremlins can't break an application, congrats! The application is robust enough to be released to real users.

This practice, also known as [Monkey testing](http://en.wikipedia.org/wiki/Monkey_test) or [Fuzz testing](http://en.wikipedia.org/wiki/Fuzz_testing), is very common in mobile application development (see for instance the [Android Monkey program](http://developer.android.com/tools/help/monkey.html)). Now that frontend (MV*, d3.js, Backbone.js, Angular.js, etc.) and backend (Node.js) development use persistent JavaScript applications, this technique becomes valuable for web applications.
This practice, also known as [Monkey testing](http://en.wikipedia.org/wiki/Monkey_test) or [Fuzz testing](http://en.wikipedia.org/wiki/Fuzz_testing), is very common in mobile application development (see for instance the [Android Monkey program](http://developer.android.com/tools/help/monkey.html)). Now that frontend (MV\*, d3.js, Backbone.js, Angular.js, etc.) and backend (Node.js) development use persistent JavaScript applications, this technique becomes valuable for web applications.

## Basic Usage

A gremlins *horde* is an army of specialized gremlins ready to mess up your application. *unleash* the gremlins to start the stress test:
A gremlins _horde_ is an army of specialized gremlins ready to mess up your application. _unleash_ the gremlins to start the stress test:

```js
const horde = gremlins.createHorde();
horde.unleash();
// gremlins will act randomly, at 10 ms interval, 1000 times
```

`gremlins.js` provides several gremlin *species*: some click everywhere on the page, others enter data in form inputs, others scroll the window in every possible direction, etc.
`gremlins.js` provides several gremlin _species_: some click everywhere on the page, others enter data in form inputs, others scroll the window in every possible direction, etc.

You will see traces of the gremlins actions on the screen (they leave red traces) and in the console log:

Expand All @@ -39,7 +38,7 @@ gremlin scroller scroll to 100 25
...
```

A horde also contains *mogwais*, which are harmless gremlins (or, you could say that gremlins are harmful mogwais). Mogwais only monitor the activity of the application and record it on the logger. For instance, the "fps" mogwai monitors the number of frame per second, every 500ms:
A horde also contains _mogwais_, which are harmless gremlins (or, you could say that gremlins are harmful mogwais). Mogwais only monitor the activity of the application and record it on the logger. For instance, the "fps" mogwai monitors the number of frame per second, every 500ms:

```
mogwai fps 33.21
Expand All @@ -58,7 +57,7 @@ mogwai fps 15.76
...
```

After 10 errors, a special mogwai stops the test. He's called *Gizmo*, and he prevents gremlins from breaking applications bad. After all, once gremlins have found the first 10 errors, you already know what you have to do to make your application more robust.
After 10 errors, a special mogwai stops the test. He's called _Gizmo_, and he prevents gremlins from breaking applications bad. After all, once gremlins have found the first 10 errors, you already know what you have to do to make your application more robust.

If not stopped by Gizmo, the default horde stops after roughly 1 minute. You can increase the number of gremlins actions to make the attack last longer:

Expand All @@ -72,7 +71,7 @@ Gremlins, just like mogwais, are simple JavaScript functions. If `gremlins.js` d
```js
// add a new custom gremlin to blur the currently focused element
horde.gremlin(function() {
document.activeElement.blur();
document.activeElement.blur();
});
```

Expand All @@ -87,21 +86,21 @@ In the browser, the `gremlins.min.js` file can be used as a standalone library,
```html
<script src="path/to/gremlins.min.js"></script>
<script>
gremlins.createHorde().unleash();
gremlins.createHorde().unleash();
</script>
```

Alternately, you can include `gremlins.min.js` as a RequireJS module, leaving the global namespace clean:

```js
require.config({
paths: {
gremlins: 'path/to/gremlins.min'
}
paths: {
gremlins: 'path/to/gremlins.min',
},
});

require(['gremlins'], function(gremlins) {
gremlins.createHorde().unleash();
gremlins.createHorde().unleash();
});
```

Expand All @@ -116,44 +115,46 @@ By default, all gremlins and mogwais species are added to the horde.
You can also choose to add only the gremlins species you want, using the `gremlin()` function of the `horde` object:

```js
gremlins.createHorde()
.gremlin(gremlins.species.formFiller())
.gremlin(gremlins.species.clicker().clickTypes(['click']))
.gremlin(gremlins.species.toucher())
.gremlin(gremlins.species.scroller())
.gremlin(function() {
window.$ = function() {};
})
.unleash();
gremlins
.createHorde()
.gremlin(gremlins.species.formFiller())
.gremlin(gremlins.species.clicker().clickTypes(['click']))
.gremlin(gremlins.species.toucher())
.gremlin(gremlins.species.scroller())
.gremlin(function() {
window.$ = function() {};
})
.unleash();
```

If you just want to add your own gremlins in addition to the default ones, use the `allGremlins()` function:

```js
gremlins.createHorde()
.allGremlins()
.gremlin(function() {
window.$ = function() {};
})
.unleash();
gremlins
.createHorde()
.allGremlins()
.gremlin(function() {
window.$ = function() {};
})
.unleash();
```

To add just the mogwais you want, use the `mogwai()` and `allMogwais()` method the same way.

`gremlins.js` currently provides a few gremlins and mogwais:

* [clickerGremlin](src/species/clicker.js) clicks anywhere on the visible area of the document
* [toucherGremlin](src/species/toucher.js) touches anywhere on the visible area of the document
* [formFillerGremlin](src/species/formFiller.js) fills forms by entering data, selecting options, clicking checkboxes, etc
* [scrollerGremlin](src/species/scroller.js) scrolls the viewport to reveal another part of the document
* [typerGremlin](src/species/typer.js) types keys on the keyboard
* [alertMogwai](src/mogwais/alert.js) prevents calls to alert() from blocking the test
* [fpsMogwai](src/mogwais/fps.js) logs the number of frames per seconds (FPS) of the browser
* [gizmoMogwai](src/mogwais/gizmo.js) can stop the gremlins when they go too far
- [clickerGremlin](src/species/clicker.js) clicks anywhere on the visible area of the document
- [toucherGremlin](src/species/toucher.js) touches anywhere on the visible area of the document
- [formFillerGremlin](src/species/formFiller.js) fills forms by entering data, selecting options, clicking checkboxes, etc
- [scrollerGremlin](src/species/scroller.js) scrolls the viewport to reveal another part of the document
- [typerGremlin](src/species/typer.js) types keys on the keyboard
- [alertMogwai](src/mogwais/alert.js) prevents calls to alert() from blocking the test
- [fpsMogwai](src/mogwais/fps.js) logs the number of frames per seconds (FPS) of the browser
- [gizmoMogwai](src/mogwais/gizmo.js) can stop the gremlins when they go too far

### Configuring Gremlins

All the gremlins and mogwais provided by `gremlins.js` are *configurable functions*, i.e. you can alter the way they work by calling methods on them.
All the gremlins and mogwais provided by `gremlins.js` are _configurable functions_, i.e. you can alter the way they work by calling methods on them.

For instance, the clicker gremlin is a function that you can execute it directly:

Expand All @@ -165,18 +166,19 @@ clickerGremlin(); // trigger a random mouse event in the screen:
In JavaScript, functions are objects, and as such can have methods. The clicker gremlin function offers customizing methods:

```js
gremlins.species.clicker()
.clickTypes(['click']) // which mouse event types will be triggered
.canClick(function(element) {
// only click elements in bar
return $(element).parents('#bar').length;
// when canClick returns false, the gremlin will look for another
// element to click on until maxNbTries is reached
})
.showAction(function(x, y) {
// by default, the clicker gremlin shows its action by a red circle
// overriding showAction() with an empty function makes the gremlin action invisible
})
gremlins.species
.clicker()
.clickTypes(['click']) // which mouse event types will be triggered
.canClick(function(element) {
// only click elements in bar
return $(element).parents('#bar').length;
// when canClick returns false, the gremlin will look for another
// element to click on until maxNbTries is reached
})
.showAction(function(x, y) {
// by default, the clicker gremlin shows its action by a red circle
// overriding showAction() with an empty function makes the gremlin action invisible
});
```

Each particular gremlin or mogwai has its own customization methods, check the source for details.
Expand All @@ -196,31 +198,31 @@ horde.seed(1234);

Before starting the attack, you may want to execute custom code. This is especially useful to:

* Start a profiler
* Disable some features to better target the test
* Bootstrap the application
- Start a profiler
- Disable some features to better target the test
- Bootstrap the application

For this usage, the `horde` object provides a `before()` method, which accepts a callback:

```js
horde.before(function startProfiler() {
console.profile('gremlins');
console.profile('gremlins');
});
```

To clean up the test environment, the `horde` object also provides an `after()` method.

```js
horde.after(function stopProfiler() {
console.profileEnd();
console.profileEnd();
});
```

Both `before()` and `after()` support asynchronous callbacks:

```js
horde.before(function waitFiveSeconds(done) {
window.setTimeout(done, 5000);
window.setTimeout(done, 5000);
});
```

Expand All @@ -229,10 +231,12 @@ horde.before(function waitFiveSeconds(done) {
By default, gremlins will attack in random order, in a uniform distribution, separated by a delay of 10ms. This attack strategy is called the [distribution](src/strategies/distribution.js) strategy. You can customize it using the `horde.strategy()` method:

```js
horde.strategy(gremlins.strategies.distribution()
.delay(50) // wait 50 ms between each action
.distribution([0.3, 0.3, 0.3, 0.1]) // the first three gremlins have more chances to be executed than the last
)
horde.strategy(
gremlins.strategies
.distribution()
.delay(50) // wait 50 ms between each action
.distribution([0.3, 0.3, 0.3, 0.1]) // the first three gremlins have more chances to be executed than the last
);
```

Note that if using default gremlins, there are [five type of gremlins](https://github.com/marmelab/gremlins.js/blob/master/src/main.js#L12). The previous example would give a 0 value to last gremlin specie.
Expand All @@ -249,10 +253,18 @@ By default, gremlins.js logs all gremlin actions and mogwai observations in the

```js
const customLogger = {
log: function(msg) { /* .. */ },
info: function(msg) { /* .. */ },
warn: function(msg) { /* .. */ },
error: function(msg) { /* .. */ }
log: function(msg) {
/* .. */
},
info: function(msg) {
/* .. */
},
warn: function(msg) {
/* .. */
},
error: function(msg) {
/* .. */
},
};
horde.logger(customLogger);
```
Expand All @@ -267,7 +279,7 @@ All contributions are welcome. New gremlins, new mogwais, new strategies, should

While developing, you can use the command `make watch` to prevent from rebuilding at each step. In this case, just include the library using:

``` html
```html
<script src="http://localhost:8080/gremlins.min.js"></script>
```

Expand Down
28 changes: 6 additions & 22 deletions src/index.js
Expand Up @@ -51,10 +51,7 @@ export default () => {
const inject = (services, objects) => {
for (let i = 0, count = objects.length; i < count; i++) {
for (let name in services) {
if (
typeof objects[i][name] === 'function' &&
!objects[i][name]()
) {
if (typeof objects[i][name] === 'function' && !objects[i][name]()) {
objects[i][name](services[name]);
}
}
Expand Down Expand Up @@ -453,36 +450,23 @@ export default () => {
gremlins.strategy(gremlins.strategies.bySpecies());
}

const gremlinsAndMogwais = [
...gremlins._gremlins,
...gremlins._mogwais,
];
const gremlinsAndMogwais = [...gremlins._gremlins, ...gremlins._mogwais];
const allCallbacks = [
...gremlinsAndMogwais,
...gremlins._strategies,
...gremlins._beforeCallbacks,
...gremlins._afterCallbacks,
];
inject(
{ logger: gremlins._logger, randomizer: gremlins._randomizer },
allCallbacks
);
const beforeCallbacks = [
...gremlins._beforeCallbacks,
...gremlins._mogwais,
];
inject({ logger: gremlins._logger, randomizer: gremlins._randomizer }, allCallbacks);
const beforeCallbacks = [...gremlins._beforeCallbacks, ...gremlins._mogwais];
const afterCallbacks = [
...gremlins._afterCallbacks,
...gremlinsAndMogwais
.map(beast => beast.cleanUp)
.filter(cleanUp => typeof cleanUp === 'function'),
...gremlinsAndMogwais.map(beast => beast.cleanUp).filter(cleanUp => typeof cleanUp === 'function'),
];

const horde = gremlins;

const strategies = horde._strategies.map(strat =>
strat(gremlins._gremlins, ...params)
);
const strategies = horde._strategies.map(strat => strat(gremlins._gremlins, ...params));

await executeInSeries(beforeCallbacks, [], horde);
await Promise.all(strategies);
Expand Down
3 changes: 1 addition & 2 deletions src/mogwais/fps.js
Expand Up @@ -75,8 +75,7 @@ export default () => {
window.requestAnimationFrame(measure);
};
const measure = time => {
const fps =
time - lastTime < NEXT_FRAME_MS ? 60 : 1000 / (time - lastTime);
const fps = time - lastTime < NEXT_FRAME_MS ? 60 : 1000 / (time - lastTime);
const level = config.levelSelector(fps);
config.logger[level]('mogwai ', 'fps ', fps);
};
Expand Down
27 changes: 2 additions & 25 deletions src/species/clicker.js
Expand Up @@ -145,38 +145,15 @@ export default () => {

const evt = document.createEvent('MouseEvents');
const clickType = config.randomizer.pick(config.clickTypes);
evt.initMouseEvent(
clickType,
true,
true,
window,
0,
0,
0,
posX,
posY,
false,
false,
false,
false,
0,
null
);
evt.initMouseEvent(clickType, true, true, window, 0, 0, 0, posX, posY, false, false, false, false, 0, null);
targetElement.dispatchEvent(evt);

if (typeof config.showAction === 'function') {
config.showAction(posX, posY, clickType);
}

if (config.logger && typeof config.logger.log === 'function') {
config.logger.log(
'gremlin',
'clicker ',
clickType,
'at',
posX,
posY
);
config.logger.log('gremlin', 'clicker ', clickType, 'at', posX, posY);
}
};

Expand Down

0 comments on commit 67062da

Please sign in to comment.