Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 938
Mike Avatar asked Dec 18 '15 17:12

Mike


3 Answers

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.

like image 122
Brock Adams Avatar answered Nov 16 '22 02:11

Brock Adams


it turns out there are several different events that trigger validation on various websites (input, keyup, change). here is the function i use that seems to work on all websites (jquery, angularjs, or plain old javascript):

function fireChangeEvents(element){
    var changeEvent = null;
    changeEvent = document.createEvent ("HTMLEvents");
    changeEvent.initEvent ("input", true, true);
    element.dispatchEvent (changeEvent);
    console.debug('input event dispatched for element: '+element.id);
    changeEvent = document.createEvent ("HTMLEvents");
    changeEvent.initEvent ("keyup", true, true);
    element.dispatchEvent (changeEvent);
    console.debug('keyup event dispatched for element: '+element.id);
    changeEvent = document.createEvent ("HTMLEvents");
    changeEvent.initEvent ("change", true, true);
    element.dispatchEvent (changeEvent);
    console.debug('change event dispatched for element: '+element.id);
}

in this specific case, i think this jquery should work:

fireChangeEvents(document.getElementById('Username'));

you might want to be careful to only fire the "change" event if the value was actually changed. i have seen some sloppy code on checkboxes toggling "valid" on every "change" event rather than reading the actual checkbox value.

note: tested on chrome 46.0.2490.71 (portable_apps edition)

like image 45
james turner Avatar answered Nov 16 '22 01:11

james turner


I was not able to get it work using the code here to set a contact field in the nextcloud contacts app (that is written in angular). This is what worked for me:

function setAngularInputValue (targetInput, newValue) {
    targetInput.value = newValue;
    const event       = new Event('input', { bubbles: true });

    targetInput.dispatchEvent(event);
}
like image 2
Thomas Kekeisen Avatar answered Nov 16 '22 02:11

Thomas Kekeisen