/ events

Programming Slack link previews in Python

Our Slack alerts give real-time and accurate location events to our customers. These alerts include an image showing the location activity on a map to provide our customers the complete picture to act upon. Link previews are obtained by capturing a screenshot of our dashboard view for the live event. In this post we take a peek under the hood to see what powers these screenshots and how you can setup a service to do the same.

Slack previews

What you need

  1. Google Chrome: a web browser to load the page
  2. Selenium WebDriver: a tool to control the web browser programmatically
  3. ChromeDriver: a server that enables the WebDriver to work on Chrome
  4. Xvfb: X window server that can run on machines without display hardware
  5. WebGL: to render map tiles correctly

We have open sourced the Dockerfile that brings these elements together to power our setup. This is also hosted on Docker Hub so you can easily set up your own service with it.

docker run -it -p 4444:4444 -v /dev/shm:/dev/shm --privileged arjun27/docker-selenium:latest

Basic usage

We can use the WebDriver Python APIs to get screenshots once the service is up and running. We get these screenshots as base64 encoded strings for our alerts. We upload them to AWS S3 and attach respective links to the Slack message body.

from selenium import webdriver

WEBDRIVER_LOCATION = 'http://DOCKERHOST:4444/wd/hub'

def get_options():
    '''
    Chrome options for WebGL
    '''
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--no-sandbox')
    chrome_options.add_argument('--disable-web-security')
    chrome_options.add_argument('--use-gl')
    chrome_options.add_argument('--ignore-gpu-blacklist')
    return chrome_options.to_capabilities()
    
def get_screenshot_as_base64(link_url, window_size):
    '''
    Get screenshot as base64 from selenium cluster for given link and window size.
    '''
    selenium_url = WEBDRIVER_LOCATION
    driver = webdriver.Remote(command_executor=selenium_url, desired_capabilities=get_options())
    driver.set_window_size(*window_size)
    driver.get(link_url)
    base64_image = driver.get_screenshot_as_base64()
    driver.quit()
    return base64_image

Interact with the DOM

The WebDriver APIs are built to drive the web browser just as a user would be able to. This means you can interact with the DOM elements: click them fill in forms and also wait for them to load before you take a screenshot.

Our dashboard views load map tiles asynchronously. Therefore the driver needs to wait for map tiles to be loaded before taking a screenshot. We achieved this by adding an empty proxy DOM element that is hidden until the map tiles have loaded. The driver waits for the screenshot until it can click this proxy DOM element.

driver.get(link_url)
driver.implicitly_wait(5) # max timeout in seconds 
driver.find_element_by_id('map-loader').click()
base64_image = driver.get_screenshot_as_base64()

The Selenium WebDriver adds a superpower to our stack making it possible to share location visualizations along with Slack and other alerts. How are you going to use it?