Search code examples
javascriptjqueryangularjsgreasemonkeytampermonkey

Automate form submission, on an AngularJS website, using Tampermonkey?


I'm trying to automate some form entry on a website that I do not have the source code to. Finding the appropriate fields and filling them out with js and submitting the form programmatically is a simple enough task. But the website is built in Angular and, when the form submit is clicked, all the validation flags for the input fields pop up as if none of the fields were filled out.

Browsing several other posts, I came across the realization that I need to somehow either set the variable inside a scope, like this:

$scope.$apply(function() {
    $scope.fieldName = varname;
});

or to set the field to dirty, like this:

$scope.fieldname.$dirty = true;

Unfortunately, not having access to the code, I'm unsure of what the scope might be, or how to appropriately tell Angular that the fields on the form have been updated programmatically.

Edit

I'm using Roblox as an example for this error.
Different forms on the site such as the registration form (and forms behind the login) have validation on them, which throws the errors I mentioned.

Here's an example I made of trying to use the same logic as the login script on the register script:

// ==UserScript==
// @name         Roblox Account Create
// @namespace    http://roblox.com/
// @version      0.1
// @description  Create Roblox Account
// @author       You
// @match        https://www.roblox.com/
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @grant        none
// ==/UserScript==
/* jshint -W097 */
'use strict';

var _adj        = [ 'Cool', 'Masked', 'Bloody', 'Lame' ];
var _animals    = [ 'Hamster', 'Moose', 'Lama', 'Duck' ];
var _months     = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];

var _username   = _adj[ parseInt( Math.random( ) * _adj.length ) ], _pass = Math.random( ).toString( 36 ).substring( 2, 10 );
_username       += _animals[ parseInt( Math.random( ) * _animals.length ) ];
_username       += parseInt( Math.random( ) * 1000 );

var _dt_month   = _months[ Math.floor( Math.random( ) * ( 12 ) + 0 ) ];
var _dt_day     = Math.floor( Math.random( ) * ( 28 ) + 1 );
var _dt_year    = Math.floor( Math.random( ) * ( 2005 - 1916 + 1 ) + 1916 );

$( '#Username' ).val( _username );

$( '#Password' ).val( _pass );
$( '#PasswordConfirm' ).val( _pass );
$( '#MonthDropdown' ).val( _dt_month );
$( '#DayDropdown' ).val( _dt_day );
$( '#YearDropdown' ).val( _dt_year );

$( '#FemaleButton' ).click( );
$( '#SignupButton' ).click( );

I have tried to add both the input and change events to my calls after changing the values, but there was no change in updating the validation to the values I added to the input fields. For example:

$( '#Username' ).val( _username ).trigger( 'input' ); // Also .trigger( 'change' )

You can test both of those script by adding them to Tampermonkey, and navigating to the ssl roblox homepage.


Solution

  • The key point is that that Angular code (and similar JS) use change events to trigger their validators. So, just setting the value is not enough; you must also send a change event.

    So:

    1. Set the value.
    2. Send a change event. For Greasemonkey/Tampermonkey scripts you must be mindful of the sandboxes and jQuery conflicts for this part.
      Using a jQuery .change(), or .trigger(), from a userscript that is not injected, seldom works. Send a proper change event; see below.
    3. Since you @required jQuery (good), but used @grant none (bad), your script was causing the page to crash and you would see various errors in the console.
    4. The script has a race condition and was often firing before the inputs were ready. Waiting for window.load seems to be enough, but you may have to step up to something like waitForKeyElements() if you should see more timing problems.
    5. There may be additional timing issues with the $('#SignupButton').click (); If so, that's outside the scope of this question.
    6. DO NOT USE THIS KNOWLEDGE TO VIOLATE ANY WEBSITE'S TERMS OF SERVICE. (If applicable)

    With that, the script becomes:

    // ==UserScript==
    // @name         Roblox Account Create
    // @match        https://www.roblox.com/
    // @require      http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
    // @grant        GM_addStyle
    // ==/UserScript==
    var _adj        = [ 'Cool', 'Masked', 'Bloody', 'Lame' ];
    var _animals    = [ 'Hamster', 'Moose', 'Lama', 'Duck' ];
    var _months     = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
    var _username   = _adj[ parseInt( Math.random( ) * _adj.length ) ], _pass = Math.random( ).toString( 36 ).substring( 2, 10 );
    _username       += _animals[ parseInt( Math.random( ) * _animals.length ) ];
    _username       += parseInt( Math.random( ) * 1000 );
    var _dt_month   = _months[ Math.floor( Math.random( ) * ( 12 ) + 0 ) ];
    var _dt_day     = Math.floor( Math.random( ) * ( 28 ) + 1 );
    var _dt_year    = Math.floor( Math.random( ) * ( 2005 - 1916 + 1 ) + 1916 );
    
    window.addEventListener ("load", function load () {
        setControl ('Username', _username );
        setControl ('Password', _pass );
        setControl ('PasswordConfirm', _pass );
        setControl ('MonthDropdown', _dt_month );
        setControl ('DayDropdown', _dt_day );
        setControl ('YearDropdown', _dt_year );
        $( '#FemaleButton' ).click( );
        $( '#SignupButton' ).click( );
    } );
    
    function setControl (elemID, value) {
        var zInput  = $( '#' + elemID );
        zInput.val( value );
    
        var changeEvent = document.createEvent ("HTMLEvents");
        changeEvent.initEvent ("change", true, true);
        zInput[0].dispatchEvent (changeEvent);
    }
    


    Note that we use a valid @grant value, other than none. That's crucial to avoid conflicts with the page's javascript.