Skip to content
Latchkey

Build and Test workflow (shaka-project/shaka-player)

The Build and Test workflow from shaka-project/shaka-player, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get caching, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: shaka-project/shaka-player.github/workflows/build-and-test.yamlLicense Apache-2.0View source

What it does

This is the Build and Test workflow from the shaka-project/shaka-player repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Build and Test

on:
  pull_request: # Trigger for pull requests.
    types: [opened, synchronize, reopened, ready_for_review]
    branches:
      - main
      - v[0-9]*
  workflow_dispatch: # Allows for manual triggering.
    inputs:
      ref:
        description: "The ref to build and test."
        required: false
  schedule:
    # Run every night at midnight PST / 8am UTC, testing against the main branch.
    - cron: '0 8 * * *'

# If another instance of this workflow is started for the same PR, cancel the
# old one.  If a PR is updated and a new test run is started, the old test run
# will be cancelled automatically to conserve resources.
concurrency:
  group: ${{ github.workflow }}-${{ github.event.number || inputs.ref }}
  cancel-in-progress: true

jobs:
  build:
    name: Build Player
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref || github.ref }}
          persist-credentials: false

      - uses: actions/setup-java@v4
        with:
          distribution: zulu
          java-version: 21

      - name: Build Player
        timeout-minutes: 20
        run: python3 build/all.py --only-es5

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: player-build
          path: dist/

  build_and_test:
    needs: build
    strategy:
      matrix:
        include:
          # Run Linux browsers with xvfb, so they're in a headless X session.
          # Additionally, generate a code coverage report from Linux Chrome.
          # It should be the uncompiled build, or else we won't execute any
          # coverage instrumentation on full-stack player integration tests.
          - os: ubuntu-latest
            browser: Chrome
            extra_flags: "--use-xvfb --running_in_vm"
          - os: ubuntu-latest
            browser: Firefox
            extra_flags: "--use-xvfb --running_in_vm"
          - os: ubuntu-latest
            browser: Opera
            extra_flags: "--use-xvfb --running_in_vm"

          - os: macos-latest
            browser: Chrome
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Edge
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Opera
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Firefox
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Safari
            extra_flags: "--running_in_vm"

          - os: windows-latest
            browser: Chrome
            extra_flags: "--running_in_vm"
          - os: windows-latest
            browser: Firefox
            extra_flags: "--running_in_vm"
          - os: windows-latest
            browser: Edge
            extra_flags: "--html-coverage-report --uncompiled --running_in_vm"
          # Disabled until working properly
          # - os: windows-latest
          #   browser: Opera

      # Disable fail-fast so that one matrix-job failing doesn't make the other
      # ones end early.
      fail-fast: false

    name: ${{ matrix.os }} ${{ matrix.browser }}
    runs-on: ${{ matrix.os }}

    steps:
      # Firefox on Ubuntu appears to not have the right things installed in
      # the environment used by GitHub actions, so make sure that ffmpeg is
      # installed. Otherwise, the browser might not support some codecs that the
      # tests assume will be supported.
      - name: Install FFmpeg
        timeout-minutes: 5
        if: matrix.os == 'ubuntu-latest' && matrix.browser == 'Firefox'
        run: sudo apt -y update && sudo apt -y install ffmpeg

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref || github.ref }}
          persist-credentials: false

      # When "Chrome for Testing" is installed on Mac or Windows, it gets
      # invoked instead of simply "Chrome".  This other copy of Chrome,
      # however, doesn't seem to have basic codecs like AAC and H264 available,
      # causing many of our test scenarios to fail.  Deleting "Chrome for
      # Testing" fixes this.
      - name: 'Delete "Chrome for Testing" on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Chrome'
        run: sudo rm -rf /Applications/Google\ Chrome\ for\ Testing.app
      - name: 'Overwrite "Chrome for Testing" on Windows'
        timeout-minutes: 5
        if: matrix.os == 'windows-latest' && matrix.browser == 'Chrome'
        shell: bash
        run: choco install -y googlechrome --ignore-checksums

      - name: 'Install Opera on Ubuntu'
        timeout-minutes: 5
        if: matrix.os == 'ubuntu-latest' && matrix.browser == 'Opera'
        run: sudo apt -y update && sudo apt -y install snapd && sudo snap install opera

      - name: 'Install Opera on Windows'
        timeout-minutes: 5
        if: matrix.os == 'windows-latest' && matrix.browser == 'Opera'
        run: choco install -y opera --ignore-checksums --params '"/NoAutostart /NoDesktopShortcut /NoTaskbarShortcut"'

      # Firefox, Edge and Opera might be missing on Mac CI images.
      - name: 'Install Firefox on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Firefox'
        run: brew install --cask firefox
      - name: 'Install Edge on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Edge'
        run: brew install --cask microsoft-edge
      - name: 'Install Opera on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Opera'
        run: brew install --cask opera

      # Some CI images (self-hosted or otherwise) don't have the minimum Java
      # version necessary for the compiler (Java 21).
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: player-build
          path: dist/

      - name: Test Player
        timeout-minutes: 25
        shell: bash
        run: |
          browser=${{ matrix.browser }}

          python3 build/test.py \
            --no-build \
            --browsers "$browser" \
            --reporters spec --spec-hide-passed \
            ${{ matrix.extra_flags }}

      - name: Find coverage reports
        id: coverage
        if: always() # Even on failure of an earlier step.
        shell: bash
        run: |
          # If the "coverage" directory exists...
          if [ -d coverage ]; then
            # Find the path to the coverage output folder.  It includes the
            # exact browser version in the path, so it will vary.
            coverage_folder="$( (ls -d coverage/* || true) | head -1 )"

            # Build a folder to stage all the coverage artifacts with
            # predictable paths.  The resulting zip file will not have any
            # internal directories.
            mkdir coverage/staging/
            cp "$coverage_folder"/coverage.json coverage/staging/
            cp "$coverage_folder"/coverage-details.json coverage/staging/
            echo "${{ github.event.number }}" > coverage/staging/pr-number.json

            echo "coverage_found=true" >> $GITHUB_OUTPUT
            echo "Coverage report staged."
          else
            echo "No coverage report generated."
          fi

      - name: Upload coverage reports
        uses: actions/upload-artifact@v4
        if: ${{ always() && steps.coverage.outputs.coverage_found }}
        with:
          name: coverage
          # This will create a download called coverage.zip containing all of
          # these files, with no internal folders.
          path: |
            coverage/staging/coverage.json
            coverage/staging/coverage-details.json
            coverage/staging/pr-number.json
          # Since we've already filtered this step for instances where there is
          # an environment variable set, the file should definitely be there.
          if-no-files-found: error

      # Upload new screenshots and diffs on failure; ignore if missing
      - name: Upload screenshots
        uses: actions/upload-artifact@v4
        if: ${{ failure() }}
        with:
          name: screenshots-${{ matrix.browser }}-${{ matrix.os }}
          path: |
            test/test/assets/screenshots/*/*.png-new
            test/test/assets/screenshots/*/*.png-diff
          if-no-files-found: ignore
          retention-days: 5

  build_in_docker:
    name: Docker
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref || github.ref }}
          persist-credentials: false

      - name: Docker
        run: |
          docker build -t shaka-player-build build/docker
          docker run -v $(pwd):/usr/src --user $(id -u):$(id -g) shaka-player-build

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: Build and Test
 
on:
  pull_request: # Trigger for pull requests.
    types: [opened, synchronize, reopened, ready_for_review]
    branches:
      - main
      - v[0-9]*
  workflow_dispatch: # Allows for manual triggering.
    inputs:
      ref:
        description: "The ref to build and test."
        required: false
  schedule:
    # Run every night at midnight PST / 8am UTC, testing against the main branch.
    - cron: '0 8 * * *'
 
# If another instance of this workflow is started for the same PR, cancel the
# old one.  If a PR is updated and a new test run is started, the old test run
# will be cancelled automatically to conserve resources.
concurrency:
  group: ${{ github.workflow }}-${{ github.event.number || inputs.ref }}
  cancel-in-progress: true
 
jobs:
  build:
    timeout-minutes: 30
    name: Build Player
    runs-on: latchkey-small
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref || github.ref }}
          persist-credentials: false
 
      - uses: actions/setup-java@v4
        with:
          cache: 'maven'
          distribution: zulu
          java-version: 21
 
      - name: Build Player
        timeout-minutes: 20
        run: python3 build/all.py --only-es5
 
      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: player-build
          path: dist/
 
  build_and_test:
    timeout-minutes: 30
    needs: build
    strategy:
      matrix:
        include:
          # Run Linux browsers with xvfb, so they're in a headless X session.
          # Additionally, generate a code coverage report from Linux Chrome.
          # It should be the uncompiled build, or else we won't execute any
          # coverage instrumentation on full-stack player integration tests.
          - os: ubuntu-latest
            browser: Chrome
            extra_flags: "--use-xvfb --running_in_vm"
          - os: ubuntu-latest
            browser: Firefox
            extra_flags: "--use-xvfb --running_in_vm"
          - os: ubuntu-latest
            browser: Opera
            extra_flags: "--use-xvfb --running_in_vm"
 
          - os: macos-latest
            browser: Chrome
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Edge
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Opera
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Firefox
            extra_flags: "--running_in_vm"
          - os: macos-latest
            browser: Safari
            extra_flags: "--running_in_vm"
 
          - os: windows-latest
            browser: Chrome
            extra_flags: "--running_in_vm"
          - os: windows-latest
            browser: Firefox
            extra_flags: "--running_in_vm"
          - os: windows-latest
            browser: Edge
            extra_flags: "--html-coverage-report --uncompiled --running_in_vm"
          # Disabled until working properly
          # - os: windows-latest
          #   browser: Opera
 
      # Disable fail-fast so that one matrix-job failing doesn't make the other
      # ones end early.
      fail-fast: false
 
    name: ${{ matrix.os }} ${{ matrix.browser }}
    runs-on: ${{ matrix.os }}
 
    steps:
      # Firefox on Ubuntu appears to not have the right things installed in
      # the environment used by GitHub actions, so make sure that ffmpeg is
      # installed. Otherwise, the browser might not support some codecs that the
      # tests assume will be supported.
      - name: Install FFmpeg
        timeout-minutes: 5
        if: matrix.os == 'ubuntu-latest' && matrix.browser == 'Firefox'
        run: sudo apt -y update && sudo apt -y install ffmpeg
 
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref || github.ref }}
          persist-credentials: false
 
      # When "Chrome for Testing" is installed on Mac or Windows, it gets
      # invoked instead of simply "Chrome".  This other copy of Chrome,
      # however, doesn't seem to have basic codecs like AAC and H264 available,
      # causing many of our test scenarios to fail.  Deleting "Chrome for
      # Testing" fixes this.
      - name: 'Delete "Chrome for Testing" on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Chrome'
        run: sudo rm -rf /Applications/Google\ Chrome\ for\ Testing.app
      - name: 'Overwrite "Chrome for Testing" on Windows'
        timeout-minutes: 5
        if: matrix.os == 'windows-latest' && matrix.browser == 'Chrome'
        shell: bash
        run: choco install -y googlechrome --ignore-checksums
 
      - name: 'Install Opera on Ubuntu'
        timeout-minutes: 5
        if: matrix.os == 'ubuntu-latest' && matrix.browser == 'Opera'
        run: sudo apt -y update && sudo apt -y install snapd && sudo snap install opera
 
      - name: 'Install Opera on Windows'
        timeout-minutes: 5
        if: matrix.os == 'windows-latest' && matrix.browser == 'Opera'
        run: choco install -y opera --ignore-checksums --params '"/NoAutostart /NoDesktopShortcut /NoTaskbarShortcut"'
 
      # Firefox, Edge and Opera might be missing on Mac CI images.
      - name: 'Install Firefox on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Firefox'
        run: brew install --cask firefox
      - name: 'Install Edge on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Edge'
        run: brew install --cask microsoft-edge
      - name: 'Install Opera on Mac'
        timeout-minutes: 5
        if: matrix.os == 'macos-latest' && matrix.browser == 'Opera'
        run: brew install --cask opera
 
      # Some CI images (self-hosted or otherwise) don't have the minimum Java
      # version necessary for the compiler (Java 21).
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: player-build
          path: dist/
 
      - name: Test Player
        timeout-minutes: 25
        shell: bash
        run: |
          browser=${{ matrix.browser }}
 
          python3 build/test.py \
            --no-build \
            --browsers "$browser" \
            --reporters spec --spec-hide-passed \
            ${{ matrix.extra_flags }}
 
      - name: Find coverage reports
        id: coverage
        if: always() # Even on failure of an earlier step.
        shell: bash
        run: |
          # If the "coverage" directory exists...
          if [ -d coverage ]; then
            # Find the path to the coverage output folder.  It includes the
            # exact browser version in the path, so it will vary.
            coverage_folder="$( (ls -d coverage/* || true) | head -1 )"
 
            # Build a folder to stage all the coverage artifacts with
            # predictable paths.  The resulting zip file will not have any
            # internal directories.
            mkdir coverage/staging/
            cp "$coverage_folder"/coverage.json coverage/staging/
            cp "$coverage_folder"/coverage-details.json coverage/staging/
            echo "${{ github.event.number }}" > coverage/staging/pr-number.json
 
            echo "coverage_found=true" >> $GITHUB_OUTPUT
            echo "Coverage report staged."
          else
            echo "No coverage report generated."
          fi
 
      - name: Upload coverage reports
        uses: actions/upload-artifact@v4
        if: ${{ always() && steps.coverage.outputs.coverage_found }}
        with:
          name: coverage
          # This will create a download called coverage.zip containing all of
          # these files, with no internal folders.
          path: |
            coverage/staging/coverage.json
            coverage/staging/coverage-details.json
            coverage/staging/pr-number.json
          # Since we've already filtered this step for instances where there is
          # an environment variable set, the file should definitely be there.
          if-no-files-found: error
 
      # Upload new screenshots and diffs on failure; ignore if missing
      - name: Upload screenshots
        uses: actions/upload-artifact@v4
        if: ${{ failure() }}
        with:
          name: screenshots-${{ matrix.browser }}-${{ matrix.os }}
          path: |
            test/test/assets/screenshots/*/*.png-new
            test/test/assets/screenshots/*/*.png-diff
          if-no-files-found: ignore
          retention-days: 5
 
  build_in_docker:
    timeout-minutes: 30
    name: Docker
    runs-on: latchkey-small
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.ref || github.ref }}
          persist-credentials: false
 
      - name: Docker
        run: |
          docker build -t shaka-player-build build/docker
          docker run -v $(pwd):/usr/src --user $(id -u):$(id -g) shaka-player-build
 

What changed

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 3 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow