I have a godly typing speed of 193 words per minute with 100% accuracy. You don’t believe me? Okay, check out my certificate here provided by an infamous typing site.

Even though you have seen the certificate, you would have to be madly gullible to believe that I actually type that fast. No, I didn’t photoshop it. Neither am I that good at typing. Instead, I used my programming skills to achieve the goal. It was a fairly easy task and I got everything done in 2 hours.

I used puppeteer which is a Node.js library to control Chromium browser. It can be used to automate stuffs like clicking buttons, visiting pages, sending keypresses. I could have used selenium with python too for the same thing, but I preferred puppeteer with javascript because it is convenient to use node.js and client side vanilla javascript with window and document object exposed in the same script. Do I even make sense here? I might, later. Lets see some code.

const puppeteer = require('puppeteer');
(async () => {
    const type_url = "https://www.ratatype.com/typing-test/test/";
    const browser = await puppeteer.launch({headless: false});
    const page = await browser.newPage();
    await page.setViewport({ width: 1366, height: 768});
    await page.bringToFront();
    await page.goto(type_url, { waitUntil: 'networkidle0' });
    await page.click('button#startButton');
})();

With just 10 lines of code after installing puppeteer with npm, I got a full fledged chromium browser which opens url to the typing test and clicks start button to start the test. Awesome. Also, I came up with an interesting finding. I was always confused whether to use semicolons or not in javascript at first. Then, I came to a conclusion that it doesn’t matter anyways as I realized javascript uses ASI (Automatic Semicolon Insertion) during runtime. So, even if we leave semicolons, it will insert them based on some rules anyways. But on running the above code without semicolon in first line, to my surprise, I got an error.

/Users/mac/projects/typebeater/ratatype.js:17
(async () => {
^

TypeError: require(...) is not a function
    at Object.<anonymous> (/Users/mac/projects/typebeater/ratatype.js:17:1)
    at Module._compile (internal/modules/cjs/loader.js:1176:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1196:10)
    at Module.load (internal/modules/cjs/loader.js:1040:32)
    at Function.Module._load (internal/modules/cjs/loader.js:929:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

What happened was an ambiguity arises when using self-invoking anonymous function. For example

const a = 'hello'
(()=>{
    console.log('world')
})()

We expect above script to print ‘world’, but we get an error instead because above statement is treated as:

const a = 'hello'(()=>{
    console.log('world');
})()

Here, hello is treated as a function and called. And thus, is not a function error. So, It is safer to use semicolons in javascript.

Anyways, I got the page opened in puppeteer controlled Chromium browser, I inspected the elements of the site and noticed that all texts to be typed were available inside a <div> with class "mainTxt". I extracted all texts easily with client side vanilla javascript that can be used with page.evaluate() function provided by puppeteer.

allTexts = await page.evaluate(()=>{
		text = ''
		for(e of document.getElementsByClassName('mainTxt')[0].childNodes){
			text += e.innerText
		}
		return text
	});

I had all texts to be typed in the test in memory. Now, I had to send keypress events to simulate typing. I tried to fire keypress event in javascript with dispatchEvent method, but for some reasons, it didn’t work. I don’t know why. I tried to attach eventListener to the element, I bubbled the event to ripple all over the DOM, but none of them worked. So, I decided to send keypress directly from the operating system. I thought I could write a simple script from scratch in node to send keypress, but I gave up before trying. I tried to use robotjs, node-key-sender libraries, but they didn’t meet my expectations.

In the end, I decided to turn to my friend python and the renowned library for automating GUI stuffs, the very best, pyautogui . I had to send the variable allTexts from above node code, to the python script. I decided to send them as command line args. For example: python keypress.py 'all texts to be typed'. To do this, I used node’s built-in library called child-process. Lets see some code.

const { spawn } = require('child_process');
const pythonPath = '/usr/local/bin/python3';

args = ['keypress.py', allTexts]
const pythonProcess = spawn(pythonPath, args)

pythonProcess.stdout.on('data', (data) => {
    console.log(data.toString())
});

pythonProcess.stderr.on('data', (data) => {
    console.log(data.toString())
});

spawn calls the python script called ‘keypress.py’ with allTexts sent as argument. stdout and stderr listen for success and error messages flushed by the process respectively. data is recieved as Buffer object and has to be converted to String first to be readable.

Now, in keypress.py file, we extract all characters to be typed and press respective keys sequentially using pyautogui.press method.

texts = sys.argv[-1]
for text in texts:
    pyautogui.press(text, pause=0.033)

You can change the value of pause to tune the typing speed that you want. Final result looked like this.

Cool, isn’t it? Everything worked as expected. But I still need to validate its me who typed. So I automated login tasks with my credentials and was able to get the certification too. It was a fun thing to program. If you want to checkout the full code, here’s a link to github repo. Change credentials and beat all typing tests.