This article is the fourth in a series exploring fun, straightforward ways you can control workflows using Camunda Cloud — have a read of the others for some background if you’d like to see Camunda Cloud in action with Restzeebe, without a line of code:
- Is there an alternative to Spaghetti?
- How can you Teach a Workflow to Execute a specific task?
- Automate manual tasks with Camunda and Trello
- Send Slack Messages direct from Camunda Cloud
Do you have to create or update screenshots of web products all the time? Do you find yourself thinking, “really, this again?” Welcome to the club! It takes me about 40 seconds per screenshot, so that’s almost seven minutes for 10 screenshots alone, just looking at the pure creation. So five iterations takes more than 30 minutes.
What if I told you that you could automate and standardize the whole process in 30 minutes with Camunda Cloud and Websiteshot? So you never have to go through the manual drudgery again!
Maybe it’s not immediately obvious why you would need this process. But there are some use cases that lend themselves to it:
- Documentation: Screenshots are a visual extension of documentations that support or clarify a technical description. Especially in the environment of SaaS products, which have different views, screenshots are very helpful.
- Evolution of products: If you wonder what your product looked like six months or two years ago, you probably wish you had taken screenshots from that time.
- Visual fast-check after a release: Maybe you want to take a quick look at some (or all) views after a release. For this you can also use screenshots that give a snapshot of the latest views.
Here’s the scenario I’d like to walk you through in this tutorial:
- A Camunda Cloud process orchestrates the screenshot generation.
- We use Websiteshot to generate the screenshots. We use this SaaS because it’s quite easy to configure your URLs and the API is simple to use.
- Finally, the finished screenshots are uploaded to an S3 bucket. Of course this can be exchanged with any other destination (Google Cloud Bucket, FTP server, Dropbox etc.).
So for this tutorial, you’ll need an account for Websiteshot and AWS. The free tier offerings are perfectly sufficient for what we have in mind 🙂
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 simple steps that correspond to the scenario described above:
- Trigger screenshot job
- Wait 60 seconds for the screenshots to be generated (Websiteshot doesn’t offer webhooks yet, but it’s on the roadmap)
- 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 Node.js 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. Easy!
Which screenshots?
We are using the Websiteshot Node.js client again for this. Easy peasy.
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 Node.js this is done with a few lines of code 🙂
Grand Finale
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. 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:
In the last few minutes we’ve built a process that automatically takes screenshots from the Cloud Console. URLs can be easily replaced, so we can create as many other templates as we want and just reuse the same process. All we need to do is 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 has inspired you to automate the generation of your screenshots with the help of processes? If so, I look forward to your reports!
This blog was originally published on dev.to. If you like this great tutorial, follow Adam on Twitter, LinkedIn or GitHub
Start the discussion at forum.camunda.io