Auto populating web forms from the terminal and ! Exclamation Points in BASH

I’ve got this ridiculous workflow for work. By ridiculous I mean that I use bash scripting for so many things, and I’m finally getting to the point where I should probably rewrite everything in a real/higher-level language.

I use aliases to issue common commands, usually &&-ing multiple commands together, or executing a multi-command function. This is all fine and good. In fact, it cuts out a lot of mundane effort and wasted time while also tracking my time usage, as I started chaining watson into the commands.

One of my coworkers has been consolidating information into a single airtable database. This is amazing because it means with the right code, I’ll be able to tap into the AT API, pull event matches, copy the data locally, and be able to parse it into variables in memory. I can then execute any number of scripts via aliases that can plug in those variables for the particular task I’m executing.

One such task is creating emails in mailchimp. Setup for each campaign is tediously boring. But a solution finally struck me today:

  1. I’ve already been launching web-based tasks using open -a "/path/to/chrome" 'http://desiredurl.com' – why not see if I can populate forms via the terminal.
  2. Now that we have a reliable system via AirTable, I can fetch up-to-date info as simply as writing and executing a script.

And so I did.

But first I had to find a way to get my terminal to communicate with Chrome. After a few dozen minutes of searching I found chrome-cli. It’s a bit old, but it works great. I use OSX, so getting it up and running was a simple homebrew installation and enabling JS Apple Events via View > Developer > Allow Javascript from Apple Events.

From there I used Chrome’s dev tools inspector to find the elements I wanted to target: an input field and a button. Once I had the selectors, I tested the following code:

(function hey(){ document.querySelector('input[name="name"]').value = 'Hey, this is a test of the CLI input system'; }())

It worked! The input field populated with the text I passed into its value. Then it was a simple matter of passing the JS to chrome-cli by wrapping it in (double) quotes and executing:

chrome-cli execute "(function hey(){ document.querySelector('input[name="name"]').value = 'Hey, this is a test of the CLI input system'; }())"

The next part was tricky, and honestly, it’s the reason I’m writing this because it took me over an hour to figure this part out.

I found the button selector, went through a similar manual testing methodology as above, this time with this code:

(function hey(){
var simulateClick = function (elem) {
// Create our event (with options)
var evt = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
// If cancelled, don't dispatch our event
let canceled = !elem.dispatchEvent(evt);
};
let btn = document.querySelector('#uniqName_2_32 button');
simulateClick(btn);
})();

Everything worked! But this time when I wrapped it in double quotes and passed it to chrome-cli execute, I kept getting an error on line 10:
-bash: !elem.dispatchEvent: event not found

After more head-banging than I care to admit, I finally realized the exclamation mark was to blame. The event not found bit was really confusing!

So I tried escaping the mark first with a backslash, and then with double quotes. It passed to chrome, but when the browser’s JS interpreter tried to handle the escaped characters, it didn’t know how to do so; exclamation mark is an entirely valid bit of code in JS, namely the unary negation operator more commonly known as the logical not or complement . In this case, that simply means the result after applying the operator will be the opposite. In this case, the operand is a boolean value, so a true will be come false and vice versa.

I have to admit, I freaked out a little here. I didn’t want to ruin my somewhat terse code with a clunky if block. But then I remembered solving this problem in a non-bash-limited context with the help of the ternary operator: operand ? result true : result false;

Here’s how I did it:

(function hey(){
var simulateClick = function (elem) {
// Create our event (with options)
var evt = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
// If cancelled, don't dispatch our event
let canceled = elem.dispatchEvent(evt) ? false : true;
};
let btn = document.querySelector('#uniqName_2_32 button');
simulateClick(btn);
})();

By simply flipping the true:false result to false:true, I effective simulate the unary operator in a one-liner, albeit with 14 more characters.

But – it did the job. Wrapping it in double quotes and passing it to chrome-cli execute worked perfectly, and now I can abstract the methods above to functions and really start streamlining my workflow.

I love BASH, but I hate BASH. I should really do this in python/ruby/perl/something fun.

Leave a Reply