Javascript

How to run TamperMonkey function after window.location.href change

When automating workflows using TamperMonkey scripts, you often have a situation where you want to run some function, then set window.location.href and after that page has loaded, run another part of your code. However, since the browser physically reloads the page, the Tampermonkey script is also reloaded – hence, your original function will stop being executed.

Fixing this is easy once you grasp the concept:

  1. Before reloading the page, we set a specific key in sessionStorage, indicating what we want to do once the page has finished loading
  2. Then we can set window.location.href
  3. On each page load, we check if the key is set and, if so, we run the appropriate function and delete the key

Note that in the TamperMonkey context, this is limited to pages that are included in the @match configuration of the script (otherwise, the script won’t be executed on the new page that is being loaded):

// ==UserScript==
// @name         TamperMonkey continuation example
// @namespace    http://tampermonkey.net/
// @version      0.1
// @author       You
// @match        https://techoverflow.net
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    const continuationKey = "_my_script_continuation_key"; // This sessionstorage key

    // Check continuation
    const continuationActions = {
        "printMsg": onPrintMsg
    };
    const _continuation = sessionStorage.getItem(continuationKey);
    if(_continuation) {
        sessionStorage.removeItem(continuationKey);
        const action = continuationActions[_continuation]
        if(action) {action();}
    }

    function onPrintMsg() {
        console.log("This is run after reloading the page");
    }

    function onAltQ() {
        console.log("Will now reload the page...");
        sessionStorage.setItem(continuationKey, "printMsg");
        window.location.href = window.location.origin + "/";
    }

    function onKeydown(evt) {
        // Use https://keycode.info/ to get keys
        if (evt.altKey && evt.keyCode == 81) {
            onAltQ();
        }
    }
    document.addEventListener('keydown', onKeydown, true);
})();

 

Posted by Uli Köhler in Javascript

How to go to URL within same domain in Javascript

In order to go to an URL relative to the current domain name in Javascript, use window.location.origin like this:

window.location.href = window.location.origin + "/myurl"
Posted by Uli Köhler in Javascript

Minimal TamperMonkey template for keypress-triggered events

This template allows you to build your own TamperMonkey scripts that react on keypresses on a specific page.

This script shows an alert message when you press Alt+Q.

// ==UserScript==
// @name         TamperMonkey keypress example
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://techoverflow.net/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    function onAltQ() {
        alert("Alt Q pressed!");
    }

    function onKeydown(evt) {
        // Use https://keycode.info/ to get keys
        if (evt.altKey && evt.keyCode == 81) {
            onAltQ();
        }
    }
    document.addEventListener('keydown', onKeydown, true);
})();

 

Posted by Uli Köhler in Javascript

How to emulate Page up / Page down key in Puppeteer

To emulate a keypress to the Page up key in Puppeteer, use

await page.keyboard.press("PageUp");

To emulate a keypress to the Page down key in Puppeteer, use

await page.keyboard.press("PageDown");

 

Posted by Uli Köhler in Javascript, NodeJS, Puppeteer

How to fix Angular 9 @ViewChild Expected 2 arguments, but got 1: An argument for ‘opts’ was not provided.

Problem:

You are trying to compile your Angular 9.x application, but you see an error message like

app/my-component/my-component.component.ts:24:4 - error TS2554: Expected 2 arguments, but got 1.

24   @ViewChild(MyOtherComponent) myOtherComponent: MyOtherComponent;
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  ../node_modules/@angular/core/core.d.ts:7888:47
    7888     (selector: Type<any> | Function | string, opts: {
                                                       ~~~~~~~
    7889         read?: any;
         ~~~~~~~~~~~~~~~~~~~
    7890         static: boolean;
         ~~~~~~~~~~~~~~~~~~~~~~~~
    7891     }): any;
         ~~~~~
    An argument for 'opts' was not provided.

Solution:

Find this line in your code at the location specified in the error message:

@ViewChild(MyOtherComponent) myOtherComponent: MyOtherComponent;

and add

{static: false}

as second argument to the @ViewChild() declaration:

@ViewChild(MyOtherComponent, {static: false}) myOtherComponent: MyOtherComponent;

In most cases, you want to use static: false. See this post on StackOverflow for details on when to use static: true as opposed to static: false.

Posted by Uli Köhler in Angular, Javascript, Typescript

How to remove all event listeners from a DOM element in Javascript

You can remove all event listeners from a DOM element in Javascript by replacing the element with a deep clone of itself. elem.cloneNode(...) will not clone the event listeners of the source element.

elem.replaceWith(elem.cloneNode(true));

Full example:

var elem = document.getElementById('mybutton');
elem.replaceWith(elem.cloneNode(true));

Source for the original (partial) suggestion on StackOverflow: @Felix Kling

Posted by Uli Köhler in Javascript

NodeJS argparse minimal example

Install argparse using

npm i --save argparse

Optional argument:

const ArgumentParser = require('argparse').ArgumentParser;
const parser = new ArgumentParser({});
parser.addArgument(['-u', '--url'], {help: "The URL to use"});
const args = parser.parseArgs();

// Example usage
console.log(args.url) // null if no such argument

If you want to make an argument mandatory, use required: true:

const ArgumentParser = require('argparse').ArgumentParser;
const parser = new ArgumentParser({});
parser.addArgument(['-u', '--url'], {help: "The URL to use", required: true});
const args = parser.parseArgs();

// Example usage
console.log(args.url)

In case the user does not provide the argument, it will print

usage: CLI.js [-h] -u URL
CLI.js: error: Argument "-u/--url" is required

Positional arguments

This positional argument will always be required:

const ArgumentParser = require('argparse').ArgumentParser;
const parser = new ArgumentParser({});
parser.addArgument(['url'], {help: "The URL to use"});
const args = parser.parseArgs();

// Example usage
console.log(args.url)

 

Posted by Uli Köhler in Javascript, NodeJS

How to fix Puppetteer ‘Running as root without –no-sandbox is not supported’

Problem:

When you try to run your puppetteer application, e.g. under docker, you see this error message:

Solution:

Note: Unless you are running in a Docker or similar container, first consider running the application as non-root-user!

You have to pass the --no-sandbox option to puppeteer.launch():

const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox']
});

We recommend to use this slightly more complex solution to pass it only if the process is being run as root:

/**
 * Return true if the current process is run by the root user
 * https://techoverflow.net/2019/11/07/how-to-check-if-nodejs-is-run-by-root/
 */
function isCurrentUserRoot() {
   return process.getuid() == 0; // UID 0 is always root
}

const browser = await puppeteer.launch({
    headless: true,
    args: isCurrentUserRoot() ? ['--no-sandbox'] : undefined
});

This ensures Chromium is run in the most secure mode possible with the current user.

Posted by Uli Köhler in Javascript, NodeJS, Puppeteer

How to check if NodeJS is run by root

Use this function to determine if the current NodeJS process is being run by the root user:

function isCurrentUserRoot() {
    return process.getuid() == 0; // UID 0 is always root
}

Usage example:

if(isCurrentUserRoot()) {
    // TODO Your code for root user goes here
} else {
    // TODO Your code for NON-root user goes here!
}

This works since the UID of the root user is always 0 on Linux/Unix systems.

Note: You don’t need to

const process = require("process");

since the process object is automatically imported in any NodeJS environment

Posted by Uli Köhler in Javascript, NodeJS

How to check NODE_ENV environment variable in Node.JS

You can use process.env.NODE_ENV to check the value of the NODE_ENV environment variable in Node.JS:

if(process.env.NODE_ENV == "development") {
    // TODO your code goes here
}

or

if(process.env.NODE_ENV == "production") {
    // TODO your code goes here
}

Note that by default NODE_ENV is undefined so remember to handle that case appropriately.

Posted by Uli Köhler in Javascript, NodeJS

Minimal Koa.JS example with Router & Body parser

This is the minimal Koa.JS application which I use as a template for new NodeJS webserver applications.

#!/usr/bin/env node
const router = require('koa-router')();
const koaBody = require('koa-body');
const Koa = require('koa');
const app = new Koa();

app.use(koaBody());

router.get('/', async ctx => {
    ctx.body = "Hello world";
});

app.use(router.routes());

if (!module.parent) app.listen(3000);

Install the requirements using

npm i --save koa koa-router koa-body

and run using

node index.js

assuming you have saved our code from above in index.js.

Now (with the node index.js command still running) go to http://localhost:3000 . You should see Hello world there. Now it’s your turn to continue on your endeavour to develop the world’s greatest webservers 🙂

Posted by Uli Köhler in Javascript, NodeJS

What does ‘if (!module.parent)’ mean in NodeJS?

In NodeJS applications you often see code like

if (!module.parent) {
    app.listen(3000);
}

This means: Run app.listen(3000) only if you are running the file

Suppose this code is in index.js. In this case, the code will only be executed if you run index.js directly (i.e. using node index.js) and not if index.js is required from another file (by require('./index.js');).

If index.js is required from another Javascript module (i.e. file), module.parent will be set to that module.

Posted by Uli Köhler in Javascript, NodeJS

How to emulate the Enter key in Puppeteer

To emulate a keypress to the Enter key in Puppeteer, use

await page.keyboard.press("Enter");

The E needs to be uppercase for this to work!

Posted by Uli Köhler in Javascript, Puppeteer

How to emulate keyboard input in Puppeteer

To emulate the user typing something on the keyboard, use

await page.keyboard.type("the text");

This will type the text extremely fast with virtually no delay between the characters.

In order to simulate the finite typing speed of real users, use

await page.keyboard.type("the text", {delay: 100});

instead. The delay betwen characters in this example is 100 Milliseconds, i.e. the emulated user types 10 characters per second.

Posted by Uli Köhler in Javascript, NodeJS, Puppeteer

How to emulate TAB key press in Puppeteer

In order to emulate a tab key press in Puppeteer, use

await page.keyboard.press("Tab");

Full example:

// Minimal puppeteer example
const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch({defaultViewport: {width: 1920, height: 1080}});
  const page = await browser.newPage();
  await page.goto('https://techoverflow.net', {waitUntil: 'domcontentloaded'});
  // Press tab 10 times (effectively scrolls down on techoverflow.net)
  for (let i = 0; i < 10; i++) {
      await page.keyboard.press("Tab");
  }
  // Screenshot to verify result
  await page.screenshot({path: 'screenshot.png'});
  // Cleanup
  await browser.close();
})();

 

Posted by Uli Köhler in Javascript, Puppeteer

How to fix Puppeteer ‘Error: Unknown key: “tab”‘

Problem:

You want to emulate a tab key press using

await page.keyboard.press("tab");

but you get an error message like

(node:30594) UnhandledPromiseRejectionWarning: Error: Unknown key: "tab"
    at assert (/home/uli/dev/myproject/node_modules/puppeteer/lib/helper.js:270:11)
    at Keyboard._keyDescriptionForString (/home/uli/dev/myproject/node_modules/puppeteer/lib/Input.js:96:5)
    at Keyboard.down (/home/uli/dev/myproject/node_modules/puppeteer/lib/Input.js:44:30)
    at Keyboard.<anonymous> (/home/uli/dev/myproject/node_modules/puppeteer/lib/helper.js:112:23)
    at Keyboard.press (/home/uli/dev/myproject/node_modules/puppeteer/lib/Input.js:178:16)
    at Keyboard.<anonymous> (/home/uli/dev/myproject/node_modules/puppeteer/lib/helper.js:112:23)
    at /home/uli/dev/myproject/test.js:8:23
    at processTicksAndRejections (internal/process/task_queues.js:85:5)
(node:30594) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:30594) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Solution:

You need to use Tab, not tab ! The T needs to be uppercase!

Use

await page.keyboard.press("Tab");

Full example:

// Minimal puppeteer example
const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch({defaultViewport: {width: 1920, height: 1080}});
  const page = await browser.newPage();
  await page.goto('https://techoverflow.net', {waitUntil: 'domcontentloaded'});
  // Press tab 10 times (effectively scrolls down on techoverflow.net)
  for (let i = 0; i < 10; i++) {
      await page.keyboard.press("Tab");
  }
  // Screenshot to verify result
  await page.screenshot({path: 'screenshot.png'});
  // Cleanup
  await browser.close();
})();

 

Posted by Uli Köhler in Javascript

How to set <input> value in Puppeteer

Use this snippet to set the value of an HTML <input> element in Puppeteer:

const newInputValue = "test 123";
await page.evaluate(val => document.querySelector('.search-form-input').value = val, newInputValue);amp

Remember to replace '.search-form-input' by whatever CSS selector is suitable to select your <input>. Examples include 'input[name="username"]' or '.username > input'.

Full example:

// Minimal puppeteer example
const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://techoverflow.net', {waitUntil: 'domcontentloaded'});
  // Set input value
  const newInputValue = "test 123";
  await page.evaluate(val => document.querySelector('.search-form-input').value = val, newInputValue);
  // Screenshot to verify result
  await page.screenshot({path: 'screenshot.png'});
  // Cleanup
  await browser.close();
})();

Note that this method will work for any simple <input>, however it might not work for some heavily Javascripted inputs which you can find on some modern websites.

Posted by Uli Köhler in Javascript, Puppeteer

How to save screenshot in Puppeteer as PNG?

You can take a screenshot in Puppeteer using

await page.screenshot({path: 'screenshot.png'});

The path is relative to the current working directory.

Want to have a screenshot in a size different to 800×600? See How to set screenshot size in Puppeteer?

Full example:

// Minimal puppeteer example
const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://techoverflow.net', {waitUntil: 'domcontentloaded'});
  // Screenshot to verify result
  await page.screenshot({path: 'screenshot.png'});
  // Cleanup
  await browser.close();
})();

 

Posted by Uli Köhler in Javascript, NodeJS, Puppeteer

How to sleep in NodeJS using async/await

The easiest way to sleep using async/await in NodeJS is the sleep-promise package:

npm install --save sleep-promise

Using that package you can simply use await sleep(milliseconds) syntax like this:

const sleep = require('sleep-promise');

// In any async function:
await sleep(2000); // Wait 2000 ms

Note that this sleep() variant is fully asynchronous, IO and other asynchronous operations will still be able to continue in the background – sleep() will not block the NodeJS process.

Full example:

const sleep = require('sleep-promise');

(async () => {
    console.log("This prints immediately");
    await sleep(2000);
    console.log("This prints 2 seconds later");
})();

 

Posted by Uli Köhler in Javascript, NodeJS

NodeJS equivalent to Python’s time.sleep() using async/await

Install the sleep-promise package:

npm install --save sleep-promise

then you can use

const sleep = require('sleep-promise');

// In any async function:
await sleep(2000); // Wait 2000 ms

Full example:

const sleep = require('sleep-promise');

(async () => {
    console.log("This prints immediately");
    await sleep(2000);
    console.log("This prints 2 seconds later");
})();

 

Posted by Uli Köhler in Javascript, NodeJS