← Back Screenshots - a perfect task to automate! u11g.com
2/14/2021

Screenshots - a perfect task to automate!

#github#node#typescript#github-actions

First iteration

Let’s start with the first iteration. This is the most minimal process that meets our requirements. As you can see, the process consists of three very simple steps that correspond to the scenario described above:

  1. Trigger screenshot job
  2. Wait 60 seconds for the screenshots to be generated (Websiteshot doesn’t offer webhooks yet, but it’s on the roadmap)
  3. Upload screenshots Step 1 and 3 are service tasks, for each of which we again implement a simple worker. The second step is a timer event.

Let’s build the workers

We need workers that interact with two services. What simplifies things enormously: both Websiteshot and AWS offer NodeJS SDKs that make integration very easy.

Create screenshots

The worker is quite simple, as the actual screenshot configuration takes place within Websiteshot. Templates can be created there, which contain the parameterization and all URLs. So that the service task can be used quite flexibly, we pass the TemplateId to be used as a service task header. With this approach we don’t have to touch the worker if we want to use different templates.

export class WebsiteshotWorker { 
  constructor(private zeebeController: ZeebeController) {} 
  public create() { 
    this.zeebeController.getZeebeClient().createWorker({ 
      taskType: Worker.WEBSITESHOT_CREATE_JOB, 
      taskHandler: async (job: any, complete: any, worker: any) => { 
        const templateId = job.customHeaders.templateid; 
        if (!templateId) { 
          complete.failure("Template Id not set as header <templateid>"); 
          return; 
        } 
        logger.info(`Creating Screenshot Job for Template Id ${templateId}`); 
        const screenshotController = new ScreenshotController({ 
          projectId: ConfigController.get( ConfigParameter.WEBSITESHOT_PROJECT_ID ), 
          apikey: ConfigController.get(ConfigParameter.WEBSITESHOT_API_KEY), 
        }); 
        try { 
          const response = await screenshotController.create(templateId); 
          complete.success({ jobId: response.jobId }); 
        } catch (error) { 
          logger.error(error); 
          complete.failure("Failed to create screenshot job via websiteshot"); 
        } 
      }, 
    }); 
  } 
}

Websiteshot integration is not worth mentioning with the Library:

const response: CreateResponse = await this.websiteshotController.create({ templateId, });

Upload created screenshots

After the first worker has started the screenshot job, the second worker takes care of the next steps:

  • Fetch all created screenshots from Websiteshot.
  • Download the files temporarily
  • Upload the locally available files to S3 For this reason the worker is a bit more extensive:
export class BucketWorker { 
  constructor(private zeebeController: ZeebeController) {} 
  public create() { 
    this.zeebeController.getZeebeClient().createWorker({ 
      taskType: Worker.AWS_BUCKET_UPLOAD, 
      taskHandler: async (job: any, complete: any, worker: any) => { 
        const jobId = job.variables.jobId; 
        if (!jobId) { 
          complete.failure("Job Id not found on process context: <jobId>"); 
          return; 
        } 
        const screenshotController = new ScreenshotController({ 
          projectId: ConfigController.get( ConfigParameter.WEBSITESHOT_PROJECT_ID ), 
          apikey: ConfigController.get(ConfigParameter.WEBSITESHOT_API_KEY), 
        }); 
        const bucketController = new BucketController( 
          { 
            id: ConfigController.get(ConfigParameter.AWS_SECRET_ID), 
            secret: ConfigController.get(ConfigParameter.AWS_SECRET_KEY), 
          }, 
          ConfigController.get(ConfigParameter.AWS_BUCKET) 
        ); 
        try { 
          const getResponse: GetResponse = await screenshotController.get( jobId ); 
          const files: Array<{ url: string; name: string; }> = getResponse.jobs.map((screenshotJob) => { 
            return { url: screenshotJob.data, name: `${screenshotJob.url.name}.png`, }; 
          }); 
          files.forEach((file) => logger.info(`name: ${file.name}`)); 
          const downloadPromises = files.map((file) => DownloadController.download(file.url, file.name) ); 
          await Promise.all(downloadPromises); 
          logger.info(`Uploading Screenshots to Cloud Bucket`); 
          const uploadPromises = files.map((file) => bucketController.upload( Path.resolve(__dirname, `../..`, DOWNLOAD_FOLDER, file.name), file.name ) ); 
          await Promise.all(uploadPromises); 
          complete.success({ screenshots: uploadPromises.length }); 
        } catch (error) { 
          complete.failure("Failed to send slack message"); 
        } 
      }, 
    }); 
  } 
}

Let’s take the Worker apart a bit.

Which job?

As a parameter, the worker gets the JobId from the process context. The first worker has written the JobId returned from Websiteshot to the process context at the end. So easy game!

Which screenshots?

We are using the Websiteshot NodeJS client again for this. Easy peasy. Somehow it doesn’t get more sophisticated…

Intermediate step

In order for us to upload the screenshots to the cloud bucket we need to have them available. We take the easy way and save the screenshots temporarily before uploading them again. For this, we don’t need to do anything more than execute a few GET requests. In NodeJS this is done with a few lines of code :)

Finale Grande

This is the central task of the worker. The previous three steps were just the preparation for this step. But even this part is pretty manageable with the help of the AWS SDK.

Yikes, are we done already? Yes! In fact, with this process and the associated workers, we’ve done everything we need to take screenshots of pre-configured URLs.

And now?

Now comes the concrete example: Camunda Cloud provides a console through which users can manage clusters and clients. Now I want to have screenshots taken from the Console using a test account. For this purpose I have created the following template: I use the process shown above exactly the same way to deploy and run it in Camunda Cloud. To start a new instance you can use Restzeebe again. Once the workers are registered the service tasks are processed. The results can be viewed via the Websiteshot Console: And our screenshots end up in S3: So, without further ado, in the last few minutes we built a process that automatically takes screenshots from the Cloud Console. I don’t need to mention that the URLs can be replaced quite easily. We can create as many other templates as we want and just reuse the same process. We just need to adjust the header parameter. Pretty cool I think! You can also view, fork and modify the complete implementation in this repo: https://github.com/websiteshot/camunda-cloud-example As with the last blog posts in this series: the process can easily be extended or the flow changed. For example, if you want to use the screenshots to automatically update the documentation, you can add an approval process. If you have read the tutorial with the Trello Cards you can for example create a new Trello Card on a specific board. A responsible person can then first look at the screenshots and either approve them for upload or reject them. In case of rejection, a specific message can be sent to a Slack channel because a view is not rendered correctly. Another nice use case is the automated generation of social share images of conference speakers: at a conference there are many speakers who like to be announced via social media. Here, a template based on HTML and CSS can be parameterized so that only the parameters need to be changed. A process could eventually generate the social share images and publish them to various social media platforms. Create the template once and sit back! Maybe this tutorial inspired you to automate the generation of your screenshots with the help of processes. If so, I look forward to your reports! And with the time gained, you can now take care of more important things, such as: Let me know if the article was helpful! And if you like the content follow me on Twitter, LinkedIn or GitHub :) Header Photo by ShareGrid on [Unsplash](https://unsplash.com/s/photos/message?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText), last Photo by Rémi Bertogliati on Unsplash.