GitHub PR에서 Playwright 테스트 결과 쉽게 확인하기

GitHub PR에서 Playwright 테스트 결과 쉽게 확인하기

3주, 3일 전 게시됨

여러 작은 사이드 프로젝트을 하다 보면 테스트 환경 구성에 많은 시간을 투자하곤 합니다. 사이드 프로젝트 개발 전반에 Test-first TDD를 준수할 생각은 없지만 테스트 자동화를 구축해두고 언제든 테스트를 추가할 수 있게끔 만드는 편입니다. 산만한 저는 자잘한 프로젝트를 많이 해보는 편이라 언제 손을 놓아도 언제든 다시 작업할 수 있는 개발 환경을 만들어두려고 노력하는 편입니다.

기능에 사용자 UI가 포함되는 경우에는 종단 간 테스트를 위해 Playwright를 이용해보고 있는데, GitHub에서 테스트 결과를 확인하는 것이 불편하다고 느끼고 있었습니다. 매번 워크플로 아티팩트를 다운로드하는 대신 PR에서 링크를 열고 바로 HTML 테스트 보고서를 확인할 수 있으면 좋겠다고 생각했죠.

이번에는 Playwright 테스트 리포트를 AWS S3을 활용하여 GitHub PR에서 쉽고 빠르게 확인하는 간단한 워크플로에 대해 공유합니다.

🎭 Playwright

Playwright는 Microsoft에서 관리하는 오픈소스 E2E 테스트 프레임워크입니다. 다양한 브라우저 및 플랫폼, 프로그래밍 언어 지원 등으로 많은 인기를 끌고 있습니다. 또한 웹 스크래핑에 사용하는 사례도 쉽게 확인할 수 있습니다.

Playwright를 선택하게 된 데에는 다음과 같은 이유가 있습니다:

  • 제가 써봤던 비슷한 다른 도구들, Cypress나 Puppeteer, Selenium과 비교했을 때 가장 구성 및 실행이 편리했습니다. 브라우저 및 시스템 의존성 설치를 대부분 알아서 관리해줍니다.

  • 브라우저 UI 및 다양한 확장(VS Code Extension, MCP)을 지원하며 좋은 사용 경험을 제공합니다.

  • 공식 문서가 잘 관리되어 있으며 생태계가 활발합니다. 참고할 수 있는 글과 문서가 굉장히 풍부하며 Microsoft에서 관리하므로 짧은 시일 내에 문제가 발생할 가능성이 적습니다.

🪣 S3 버킷 생성하기

Playwright HTML 보고서를 브라우저에서 다운로드 과정 없이 쉽고 빠르게 확인하려면 간단한 정적 호스팅이 필요합니다. 임시 웹 페이지 호스팅이 간단해야 하고 여러 PR에서 생성된 보고서를 확인할 수 있어야 합니다. 또한 오래된 웹 페이지를 자동 만료시킬 수 있다면 더욱 편리하겠죠.

그래서 AWS S3를 활용하기로 했습니다. S3는 정적 웹 호스팅 기능을 지원하고 이는 디렉토리 단위로도 동작하므로 여러 PR에서 생성된 보고서에 대한 임시 웹 페이지를 호스팅할 수 있습니다. 또한 라이프사이클 규칙을 통해 오래된 보고서는 자동으로 삭제할 수 있으니, 비용 절감에도 도움이 되겠죠. 

다른 프로젝트에서도 Playwright를 사용하고 있거나 앞으로도 사용할 계획이 있기 때문에 Pulumi로 그 구성을 정의해두고 다른 프로젝트에서도 재사용하기로 했습니다.

import pulumi_aws as aws
from pulumi import Output, ResourceOptions

bucket = aws.s3.Bucket(
    "playwright-reports",
    bucket_prefix="playwright-reports-",
    force_destroy=True,
)
public_access_block = aws.s3.BucketPublicAccessBlock(
    "playwright-reports",
    bucket=bucket.id,
    block_public_acls=False,
    block_public_policy=False,
    ignore_public_acls=False,
    restrict_public_buckets=False,
)
aws.s3.BucketAcl(
    "playwright-reports",
    opts=ResourceOptions(depends_on=[public_access_block]),
    bucket=bucket.id,
    acl="public-read",
)
aws.s3.BucketOwnershipControls(
    "playwright-reports",
    bucket=bucket.id,
    rule={
        "object_ownership": "BucketOwnerPreferred",
    },
)
aws.s3.BucketPolicy(
    "playwright-reports",
    bucket=bucket.id,
    policy=aws.iam.get_policy_document(
        statements=[
            {
                "actions": ["s3:GetObject"],
                "resources": [Output.concat(bucket.arn, "/*")],
                "principals": [{"type": "*", "identifiers": ["*"]}],
            },
        ],
    ).json,
)
aws.s3.BucketWebsiteConfiguration(
    "playwright-reports",
    bucket=bucket.id,
    index_document={"suffix": "index.html"},
    error_document={"key": "error.html"},
)
aws.s3.BucketLifecycleConfiguration(
    "playwright-reports",
    bucket=bucket.id,
    rules=[
        {
            "id": "Expire reports older than 7 days",
            "status": "Enabled",
            "expiration": {
                "days": 7,
            },
        },
    ],
)

이 외에도 GitHub Actions 변수 자원을 관리하는 코드가 있어 GitHub Actions 환경으로 변수 및 비밀값 삽입 또한 Pulumi에서 처리하고 있습니다. AWS 인프라에 인증하기 위한 OIDC 구성 등도 포함해서요.

⚙️ Playwright HTML 리포트 설정하기

테스트 후 HTML 리포트를 생성하도록 Playwright 설정을 갱신해야 할 필요가 있습니다. $.reporter 설정을 변경하여 HTML 리포트를 생성하도록 해줍니다.

    reporter: [
        ['list'],
        [
            'html',
            {
                open: process.env.CI ? 'never' : 'on-failure',
                host: process.env.CONTAINER ? '0.0.0.0' : '127.0.0.1'
            }
        ],
        ['junit', { outputFile: 'junit.xml' }]
    ],

생성된 리포트는 playwright-report/ 디렉토리에 저장됩니다. 남은 것은 이 파일을 S3에 업로드하는 것 뿐입니다.

📰 S3에 HTML 리포트 호스팅하기

이제 CI 워크플로 내용을 갱신하여 HTML 리포트를 S3에 업로드하고 thollander/actions-comment-pull-request를 이용하여 PR에 댓글을 남깁니다. AWS OIDC를 이용하여 임시 인증 정보를 발급받은 뒤 S3에 업로드하며, 업로드를 위한 IAM 정책은 연관된 IAM 역할에 극히 제한적으로 부여되어 있습니다.

    steps:
      # ...

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ${{ vars.AWS_REGION }}
          role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}

      # ... Set up and run Playwright tests

      - name: Upload Playwright report to GitHub
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-artifacts
          path: |
            e2e/playwright-report/
            e2e/test-results/
            e2e/run.log
          retention-days: 7

      - name: Upload Playwright report to S3
        id: upload-playwright-report-s3
        if: always()
        env:
          PLAYWRIGHT_REPORT_S3_PREFIX: playwright-report-${{ github.run_id }}
        run: |
          aws s3 cp --recursive \
            e2e/playwright-report/ \
            "s3://${{ vars.PLAYWRIGHT_REPORTS_BUCKET }}/${{ env.PLAYWRIGHT_REPORT_S3_PREFIX }}/"

          report_index_url="https://${{ vars.PLAYWRIGHT_REPORTS_BUCKET }}.s3.${{ vars.AWS_REGION }}.amazonaws.com/${{ env.PLAYWRIGHT_REPORT_S3_PREFIX }}/index.html"
          cat <<EOF | tee ./playwright-report.md $GITHUB_STEP_SUMMARY
          📊 Playwright test report is now available at [here](${report_index_url})
          EOF

      - name: Comment on PR
        uses: thollander/actions-comment-pull-request@v3
        if: always() && steps.upload-playwright-report-s3.outcome == 'success' && github.event_name == 'pull_request'
        with:
          comment-tag: playwright-report
          file-path: ./playwright-report.md

테스트가 완료되면 PR에 댓글이 달립니다.

링크를 클릭하면 보고서를 바로 확인할 수 있습니다. 테스트 케이스의 상세 내용 또한 확인할 수 있습니다.

실패한 테스트 케이스의 경우 Playwright 설정에 따라 트레이스, 스크린샷 스냅샷의 변경 상세 등이 테스트 결과 상세에 포함되어 자세한 내용을 확인할 수 있습니다.

💭 마치며

간단한 자동화지만 이후에 테스트 결과 확인이 굉장히 빠르고 쉬워졌습니다. 하지만 여전히 더 개선했으면 하는 점들은 많이 있어서 앞으로 다음과 같은 점들을 추가로 개선하며 글을 보완하려고 합니다.

  • 최근 개방된 S3에 대한 Denial of Wallet Attack에 대한 이야기를 자주 듣게 됩니다. 이에 대한 최소한의 보안 레이어(AWS CloudFront나 Cloudflare)를 추가하고자 합니다.

  • 여러 프로젝트에서 쉽게 재사용할 수 있는 방식(GitHub App, Custom GitHub Action 등)도 구상 중입니다. 구성을 복사하여 붙여넣는 것보다 좀 더 나은 방식을 찾아보려고 합니다.

  • PR에서 스냅샷을 업데이트할 수 있다면 좀 더 편리할 것 같네요. 이번에는 다루지 않았지만 앞으로 필요해질 것 같습니다.

제 개인 프로젝트(lasuillard/raindrop-sync-chrome)에서 사용 중이니 여기서 워크플로를 확인하실 수 있습니다.

🔗 참고 문서