Preview JavaScript Build Artifacts Directly in Pull Request with Stoat
Tags: javascript, pullrequest, build, stoat
Published: 2022-12-28 04:15:01

A typical build for a JavaScript / TypeScript project can generate a number of artifacts:

- Test reports ([Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), etc.)
- Test coverage reports
- Integration test artifacts ([Cypress](https://www.cypress.io/) screenshots/videos, etc.)
- Component previews ([Storybook](https://storybook.js.org/), [Ladle](https://ladle.dev/), etc.)
- Documentation

Though useful for debugging and development, these outputs are usually disabled in continuous integration (CI) pipelines because the outputs are not easily accessible. It is almost impossible to look at those outputs from a CI pipeline running remotely. It can be equally difficult for developers to get to these outputs for local builds. Who would remember that the human-readable version of the Jest coverage report is under `coverage/lcov-report` by default?

To solve this problem, we created Stoat. It is the easiest way to make all of this information trivial to consume for both local and remote builds. In this doc, we are going to show you how to create previews for these typical JavaScript build outputs.

We are going to use this repo as an example: [penx/storybook-code-coverage](https://github.com/penx/storybook-code-coverage), authored by [Alasdair McLeay](https://github.com/penx). The author wrote a detailed article about the set-up of this repo here: [Combining Storybook, Cypress and Jest Code Coverage](https://dev.to/penx/combining-storybook-cypress-and-jest-code-coverage-4pa5). This repo uses Jest for unit tests, Cypress for integration tests, and Storybook for visual previews. Each module generates their own test coverage reports, which can be merged together into one report.
The original repo doesn't have a GitHub workflow to build the entire project, but we will add one as part of the process.

We have two goals here:
1. Write a GitHub workflow to run the CI pipeline.
2. Use Stoat to provide previews for the latest test coverage reports, static Storybook pages, and Cypress videos on every pull request.

### 1. Add GitHub workflow

Create a YAML file at `.github/workflows/ci.yaml` with the following content:

```yaml
name: Continuous Integration

on:
  # Trigger the ci pipeline for every comment on the default branch or a pull request.
  # The default branch for the sample repo is `master`.
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        
      - name: Set up node and cache
        uses: actions/setup-node@v3
        with:
          node-version: '16.8.0'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile --prefer-offline

      # This project has a `coverage` script that runs all the
      #  tests and merge all their coverage reports.
      - name: Run tests and generate report
        run: yarn coverage

      - name: Generate storybook
        run: yarn build-storybook

      - name: Run stoat action
        uses: stoat-dev/stoat-action@v0
        if: always()
```

When you set it up for your own projects, most of the YAML file can be exactly the same. The only differences are:

- Replace `master` with the name of your default branch.
- Replace `16.8.0` with the version of Node.js you are using.
- Run the test script that is set in `package.json`. Typically, it is `jest --coverage` for unit test.

Note that the Stoat action is appended at the end of the `ci` job. This one step can carry out various tasks according to the Stoat config.

### 2. Install Stoat App

Install the Stoat application [here](https://github.com/apps/stoat-app).

### 3. Configure Stoat

Create a Stoat config file at `.stoat/config.yaml`, and paste in the following content:

```yaml
---
version: 1
enabled: true
plugins:
  job_runtime:
    enabled: true
  static_hosting:
    merged-test-coverage:
      path: coverage/merged/lcov-report
    storybook:
      path: storybook-static
    cypress-video:
      path: cypress/videos/spec.js.mp4
```

The Stoat config is a YAML file that contains a list of tasks. Each task contains one Stoat plugin. In the example above, we have four tasks:

- Setting the `job_runtime` plugin to `enabled` reports the time it takes to run every GitHub job. The build data is tracked by the Stoat action attached to the `ci` job.
- Task `merged-test-coverage` looks for the test coverage report under `coverage/merged/lcov-report`, and uses the Stoat `static_hosting` plugin to upload it to the Stoat server. The entire directory will be uploaded, so the preview will include all the HTML, JavaScript, and CSS files.
- Task `storybook` is very similar. It looks for the static Storybook build under `storybook-static`, and uploads them to the Stoat server.
- Same for the `cypress-video` task. The only difference is that this task uploads a single file instead of a directory.

### 4. That's It!

Now, commit the two files to the codebase, and create a pull request.
After the build completes (about two minutes), you will be able to see the previews in a comment from the Stoat bot:

<img src="https://stoat-dev--static.stoat.page/screenshot-javascript-tutorial-ids.png" alt="javascript comment screenshot (with ids)" width={600} />

Now you can click on the links to see the previews or watch the Cypress video. This comment will be auto updated whenever the CI workflow is triggered and completed.

The Stoat comment is also highly customizable. For example, currently, in each row of the preview table, the `Name` column is just the task ID in the Stoat config. You can change it to something more friendly by adding a metadata `name` field to each of the task:

```yaml
version: 1
enabled: true
plugins:
  job_runtime:
    enabled: true
  static_hosting:
    merged-test-coverage:
      # The metadata and name fields are newly added here.
      metadata:
        name: Test coverage report
      path: coverage/merged/lcov-report
    storybook:
      metadata:
        name: Storybook
      path: storybook-static
    cypress-video:
      metadata:
        name: Cypress video
      path: cypress/videos/spec.js.mp4
```

Commit and push the change.

After another two minutes, the comment is updated with the new names and runtime chart:

<img src="https://stoat-dev--static.stoat.page/screenshot-javascript-tutorial-names.png" alt="javascript comment screenshot (with names)" width={600} />

You can find the exact pull request used for this tutorial [here](https://github.com/stoat-dev/example-javascript/pull/1).

Now you're ready to start using Stoat for your JavaScript and TypeScript projects!

Stoat can actually do much more. Check out these tutorials for more use cases:

- [View build time graph in every pull request](https://docs.stoat.dev/docs/tutorials/build-runtimes)
- [Preview generated images in pull requests](https://docs.stoat.dev/docs/tutorials/preview-images)
- [Preview generated videos in pull requests](https://docs.stoat.dev/docs/tutorials/preview-videos)