Stalking the Elgato Green Screen Using Puppeteer

My boss and I have been in an arms race over the coolest teleconferencing/streaming tech since COVID started. He got lights, I got lights. I turned an old iPhone into an extremely high quality webcamp, he bought a Sony DSLR. And so on.

At one point early on, he bought an Elgato Green Screen MT to hang from his ceiling. Then he gave me the green fabric he had been draping on the wall behind him.

To drape it on my wall, I had to turn my desk around to be facing a wall, rather than my camera pointing out into my room. We live in a small house that was optimized for us not being in it very often, so that effectively cut our room into thirds with our bed taking up most of the other 2/3. I did that for about 2 months before deciding it’s just not worth it. However, now I don’t have a green screen, so in any Google Meet meetings or any other non-Zoom camera situation, you can see the unfolded laundry on my bed. Also Zoom can’t quite tell what the space between my headphones and my head is, so you get a sliver of view into my room.

Of course, in the ensuing two months, all green screens have sold out. There are a bunch of knock-offs on Amazon, which appear to be both perfectly fine, and also terribly made. I’m not that desperate. But my first job in tech was writing end-to-end browser tests for an e-commerce platform, so I can handle this. I just need to automate opening a browser and attempting to add the green screen to a cart, every 10 min or so. It can’t be headless because I don’t want to go to the effort of automating the purchase (honestly because it’s hard to test that without accidentally purchasing a StreamDeck), but rather get a notification when it’s been added to the cart so I can go finish buying it.

All I needed was Puppeteer, a free Twilio account, browser DevTools, and cron.

What follows is not the most elegant JS, but consider this to be what you would get after the first round of whiteboarding in an interview unfortunate enough to use whiteboarding.

const puppeteer = require('puppeteer');
const accountSid = 'twilioAccountID';
const authToken = 'twilioToken';
const client = require('twilio')(accountSid, authToken);
const fs = require('fs');

(async () => {
  try {
    fs.openSync('./success.signal')
  } catch (error) {
    const now = new Date()
    const time = now.getFullYear() + "" + now.getMonth() + "" +  now.getDate() + ":" + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds();
    console.log(time)
    const browser = await puppeteer.launch({headless: false, defaultViewport: null});
    const page = await browser.newPage();
    await page.setViewport({width: 1200, height: 1000})
    try {
      await page.goto('https://www.elgato.com/en/gaming/green-screen');
      await delay(4000)
      await page.click('.EWSCookieConsentButtonAllow')
      await delay(4000)
      await page.click('body > div.dialog-off-canvas-main-canvas > div > main > div.page-container-bv-append > article > div.panel-hero.panel-fullscreen.rel > div.placeholder-wrap-hero-anim > div > div > div > div > div.btn')
      await delay(4000)
      await page.click('div.overflow-hide:nth-child(3) > div:nth-child(1) > a:nth-child(2) > div:nth-child(1) > div:nth-child(1)')
      await delay(4000)
      await page.click('#minicart-checkout-button')
    } catch (error){
      console.log(error)
      console.log('green screen was not in stock')
      await browser.close()
      process.exit()
    }

    console.log('put a green screen in the cart')
    await client.messages.create({
      to: 'mynumber',
      from: 'mytwilionumber',
      body: 'There is an elgato cart ready with a green screen!'
    })
    fs.writeFileSync('./success.signal', '')
  }
})();

function delay(time) {
  return new Promise(function(resolve) {
      setTimeout(resolve, time)
  });
}

Since moving to more DevOps type work, I haven’t had a lot of reason to use Firefox/Chrome DevTools, but man do I love the ability to Copy > CSS Selector. That made targeting the buttons incredibly easy on a page for which I don’t control HTML properties.

As with many things, the problem was not in writing the code, of course, but in deploying it.

The first place I tried to deploy it was a Raspberry Pi 3+. Puppeteer doesn’t support installing Chromium on ARM. Ok, I installed Chromium the normal way, but then opening a single browser window and attempting to automate it caused the Pi to run out of memory and reboot. Great.

Right now I’m doing almost all my work on Windows in WSL2. Which doesn’t support Chromium from Puppeteer, or Chrome, because Chrome can only be installed via snap which is not currently supported by WSL2. Apparently you can do it in Firefox using a display server, but I didn’t want to work that hard on this.

So I grabbed an old Macbook Pro, and installed Amphetamine and Amphetamine Enhancer, which will allow me to keep the thing running with the lid closed, and for the screensaver to turn on.

The key thing in the code above, since this is running on a 10 minute cycle and it could succeed in the middle of the night, and potentially put 80 screens in 80 carts before I wake up to turn off the cron, is borrowed from Postgres12 recovery. If it succeeds, it drops a success.signal file in the directory, that it looks for on startup. If it can open that file, it knows there’s already a populated cart, and exits. If it can’t open that file, it moves on with the rest of the script.

The cron, then, is

*/10 * * * * /Users/dsudia/.nvm/versions/node/v14.5.0/bin/node /Users/dsudia/code/elgato-checker/index.js >> /Users/dsudia/code/elgato-checker/log.txt

I don’t have my green screen yet, but fingers are crossed.