written by
Alan Richardson

Automating Deselection and Unchecking of Twitter Interests

eviltester.com 6 min read

TLDR; I automated the unchecking of all Twitter Interests using simple JavaScript at the console. I explain the code I wrote, and how I wrote it to act as a case study in Tactical Automation.

Twitter Interests have been automatically collated. You can manually review and amend these (there might be hundreds). I decided to uncheck them all. And did so automatically.

Final Code

What follows is a blog post explaining how I arrived at the following code.

Code is also on Gist

If you just want code that you can paste into the console of your dev tools to deslect your Twitter interests then it is below (I paste it from a single line, but it is formatted here for readability and should work if copy and pasted) :

var timer=100;document.querySelectorAll(
"div > input[type='checkbox']:checked"
).forEach((interest) => {
setTimeout(function(){interest.click()},100+timer);timer+=100;});

Instructions:

You will probably see some 500 errors in the console. You may need to wait a few minutes and repeat the script. Then revisit it a few hours later and may need to repeat the script again.

Context

I saw a few tweets come through my feed on a Sunday evening about Twitter Interests.

https://twitter.com/settings/your_twitter_data/twitter_interests

Twitter seems to have automatically gathered some 'interests' and assigned them to our accounts. Probably to allow ads to be more targeted.

I'm not sure who created the algorithm used but after scanning the list it was not well built for me.

I decided to uncheck them all.

A quick 'find' in the dev tools showed me that Twitter had created 307 of these 'interests'

I used the following CSS selector to find out how many there were:

div > input[type='checkbox']

Rather than click through 307 to deselect them all, I decided to automate it.

A Process of Automating Tactically

This is an example of tactical automation.

  • It gets the job done
  • It is unlikely to be maintained
  • It has no support agreement
  • Once used, it is unlikely to be used often
  • It may need to be tweaked each time it is run

Can I get all the checkboxes to allow me to iterate over them?

I already know how to find all the checkboxes

div > input[type='checkbox']

Do I know how to get all the checked, checkboxes?

If I don't then I do a web search for "CSS Selector checked checkbox"

And then identify this:

div > input[type='checkbox']:checked

I test that in the dev tools console with the find command.

I uncheck a few, run the CSS selector and see the number has reduced.

I can start using this CSS Selector in code.

Can I uncheck a checkbox programmatically?

To uncheck manually I click the checkbox. Does the 'click()' method on an element do what I need for this application?

I check this by finding the first unchecked checkbox and issuing a click.

document.querySelector("div > input[type='checkbox']:checked").click();

That issued some network requests and unchecked the checkbox, so I'll use that.

Can I work through the list?

My first thought was a for loop, clicking, but I've tried things like that before and since JavaScript is single threaded that doesn't always work, so I tend to use setTimeout in combination with looping to do some work.

My initial thought was.

create a function which

  • finds the next item,
  • scrolls it into view
  • clicks it
  • then schedules another call to the function in 500 milliseconds

So I wrote that.

function clearNextInterest(){
var selector="div > input[type='checkbox']:checked";
var next=document.querySelector(selector);
if(next){
next.scrollIntoView();
next.click();
setTimeout(clearNextInterest,500)
};
};
clearNextInterest();

SetTimeout calls some code or a function, after X milliseconds has passed so this is basically queing up a call to itself ever 500 milliseconds.

The above seemed to work. And I could experiment with different milliseconds between calls if I wanted.

It did throw some 500 errors as it ran. Which I think is the server saying "ouch, too many too fast". But then I just wait a while and run it again.

What I didn't like about this was that it was recursive, so if there were a lot of items in the lists it might blow the callstack, so I decided to re-write it prior to letting anyone know.

Elegance - loop over the elements and uncheck each

Could I instead loop over the elements and for each element, call a function to uncheck it?

When looping over elements I tend to create a whole bunch of offset SetTimeout calls, this presumeably is also on a stack somewhere so might have a similar risk as the recursive call, but It 'feels' slightly less risky.

I tend to create an offset time which I iterate over each loop. Then run through all the elements quickly, queuing up the calls to the 'do something with this element' functionality.

var timer=100;
document.querySelectorAll(
"div > input[type='checkbox']:checked"
).forEach((interest) => {
setTimeout(
function(){interest.click()},100+timer
);
timer+=100;
});

What this does is,

  • set a 'timer' to 100 which is the initial delay for the functionality.
  • find all the checkboxes which are checked
  • for each checkbox
    • create a timeout so the click event is fired in 100 milliseconds plus current timer value
    • increase the timer by 100 milliseconds

This is a crude way of stacking each call into the future about 100 milliseconds apart.

Again timing could be altered.

Options

I only explored two ways of doing this.

There will be other ways this could be done.

There will be more elegant code that could be written.

I wrote code that got the job done, and didn't take long to write (because I already knew the basics of how to automate from the browser and have practiced this on other apps).

I automated it tactically.

And shared it so that:

  • other people might be able to use the code
  • encourage other people to learn to automate tactically
  • demonstrate that you can achieve a lot, with a little

500 errors

As this code runs, there are 500 errors from some of the promises so some of the boxes are unchecked, but when the page is refreshed the items are still checked.

Perhaps the server is busy - I didn't investigate the content of the 500 error.

I would have done if this was strategic, because I would want to find a way of making this reliable, error free and repeatable.

Because this is tactical - I can run it again until it works.

But I also think this 500 error happens when you uncheck everything manually because I saw a lot of tweets saying:

  • "I unselected them all and now twitter has re-selected half again"

I suspect what happened is that 'half' of them received 500 errors during the manual deselection, but you only see this at the console and people didn't notice. So when they look again later, they are still selected.

I have no proof of this, but it I was testing this system, that would be the hypothesis I start with as I investigated the reports from the public.

Makes Sense?

This is a good use case for how knowing JavaScript can help for adhoc tasks.

And if you are interested in learning how to do this more then you can check out my free course on TestAutomationU

testing