diff --git a/playwright-gh-pages/README.md b/playwright-gh-pages/README.md
index 5edd2a1a..e2a49aba 100644
--- a/playwright-gh-pages/README.md
+++ b/playwright-gh-pages/README.md
@@ -1,15 +1,15 @@
-# Publishing Playwright reports to Github Pages
+# Publishing Playwright reports to Google Cloud Storage
-When testing a Grafana plugin using the [`@grafana/plugin-e2e`](https://www.npmjs.com/package/@grafana/plugin-e2e?activeTab=readme) package, it is highly recommended to run tests against a matrix of Grafana versions (as demonstrated in this [example](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci) in the documentation). Each test run in this matrix generates an HTML report. By uploading these reports to a static site hosting service, they become immediately accessible for direct browsing, eliminating the need to download and serve them locally. This enhances productivity and fosters collaborative troubleshooting by making the results easily shareable and reviewable.
+When testing a Grafana plugin using the [`@grafana/plugin-e2e`](https://www.npmjs.com/package/@grafana/plugin-e2e?activeTab=readme) package, it is highly recommended to run tests against a matrix of Grafana versions (as demonstrated in this [example](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci) in the documentation). Each test run in this matrix generates an HTML report. By uploading these reports to Google Cloud Storage, they become immediately accessible for direct browsing by Grafana staff, eliminating the need to download and serve them locally.
-This set of GitHub Actions streamlines the process of managing Playwright test reports. It automates uploading reports as artifacts, publishing them to GitHub Pages, and providing links in pull request comments. These actions work seamlessly together, enhancing collaboration, traceability, and test result management.
+This set of GitHub Actions streamlines the process of managing Playwright test reports. It automates uploading reports as artifacts, publishing them to GCS, and providing links in pull request comments.
## Overview
The workflow consists of two main actions:
-1. **Upload Report Artifacts Action**: This action uploads test reports and summaries as GitHub artifacts. It can be used together with the `deploy-report-pages` action to publish the reports to GitHub Pages.
-2. **Deploy to GitHub Pages Action**: This action publishes the test artifacts to GitHub Pages and comments on the pull request with the results and corresponding links. It also cleans up and deletes old reports based on the specified retention policy.
+1. **Upload Report Artifacts Action**: Uploads test reports and summaries as GitHub artifacts. Used together with the `deploy-report-pages` action to publish the reports to GCS.
+2. **Deploy Playwright Reports to GCS Action**: Uploads the test artifacts to Google Cloud Storage and comments on the pull request with the results and corresponding links.
## Features
@@ -19,38 +19,38 @@ The workflow consists of two main actions:
- Supports conditional uploading based on test outcomes.
- Structures reports in a well-organized directory format, ensuring uniqueness for each test setup.
-- **Deploy to GitHub Pages Action**:
- - Downloads test artifacts and publishes them to GitHub Pages.
+- **Deploy Playwright Reports to GCS Action**:
+ - Downloads test artifacts and uploads them to Google Cloud Storage.
- Comments on the pull request with the test results and links to the reports.
- - Supports retention of reports for a specified number of days.
+ - Reports are retained for **90 days** via the bucket's object lifecycle policy.
-## GitHub Pages Branch Configuration
+## GCS Bucket Configuration
-The `deploy-report-pages` Action publishes reports to the `gh-pages` branch by default. However, if GitHub Pages isn't already configured to serve content from this branch, you'll need to take an extra step to ensure your reports are accessible. Follow these steps:
+Reports are uploaded to a Grafana-managed GCS bucket. Each report is stored at the path:
-1. Navigate to the **Settings** tab of your repository.
-2. In the left-hand sidebar, click on **Pages**.
-3. Under **Source**, select **Deploy from a branch**, then choose `gh-pages`.
+```
+gs://{bucket}/{owner}/{repo}/{YYYYMMDD}/{pr-number-or-run-id}/{matrix-dir}/
+```
-This setup must be completed **manually** before GitHub Pages can build and serve reports from the `gh-pages` branch. Once configured, GitHub will automatically update the site whenever new reports are deployed.
+The bucket must be provisioned by Grafana infra with:
-If you’re already using GitHub Pages for other content, ensure that it does not overwrite reports published by the `upload-report-artifacts` Action.
+- **Object lifecycle rule** — auto-delete objects after 90 days.
+- **IAM** — `roles/storage.objectViewer` granted to the Grafana Google Workspace group; uniform bucket-level access enabled.
+- **WIF write access** — the GitHub Actions service account used by Grafana's org-wide Workload Identity provider must have object create/write on the bucket.
-## GitHub Pages Visibility
+## Viewing Reports
-By default, all GitHub Pages sites are publicly accessible on the Internet. Published Playwright reports can include HTML output, screenshots, traces, and internal URLs, so review the contents before enabling this workflow for sensitive repositories. GitHub Enterprise customers can restrict access by configuring access control for private and internal repositories. For more details, refer to the official GitHub [documentation](https://docs.github.com/en/enterprise-cloud@latest/pages/getting-started-with-github-pages/changing-the-visibility-of-your-github-pages-site#about-access-control-for-github-pages-sites).
+Report links in PR comments point to `https://storage.cloud.google.com/{bucket}/...`. Viewing them requires being signed into a Google account that is a member of the Grafana Google Workspace group. Users not in the group will see a 403 error.
## Permissions Needed
Use job-scoped permissions instead of granting write access to the entire workflow:
- Set `contents: read` at the workflow level so test jobs can check out the repository.
-- Add `contents: write` only to the job that publishes reports to the Pages branch.
+- Add `id-token: write` only to the job that publishes reports to GCS (required for Workload Identity Federation authentication).
- Add `pull-requests: write` only if you want `deploy-report-pages` to manage a pull request comment. This permission is not needed when `pr-comment-summary: false`.
-The `playwright-gh-pages` actions do not use OIDC, so `id-token: write` is not required for report publishing.
-
-If your workflow runs on `pull_request`, remember that pull requests from forks receive a read-only `GITHUB_TOKEN`. In that case the publish job may be unable to push the Pages branch or update the PR comment. Avoid switching these examples to `pull_request_target` just to get a write-capable token unless you have separately reviewed the security implications of running untrusted code with elevated permissions.
+Note: `contents: write` is **not** required. This action does not push to any branch.
## Workflow usage
@@ -136,21 +136,20 @@ jobs:
with:
test-outcome: ${{ steps.run-tests.outcome }}
- deploy-pages:
+ deploy-reports:
if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) }}
needs: [playwright-tests]
runs-on: ubuntu-latest
permissions:
- contents: write
+ id-token: write
pull-requests: write
steps:
- - uses: actions/checkout@v4
-
- # use deploy-report-pages Action to deploy the artifacts to GitHub Pages
+ # use deploy-report-pages Action to upload the artifacts to GCS
- name: Publish report
uses: grafana/plugin-actions/playwright-gh-pages/deploy-report-pages@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+ bucket: your-gcs-bucket-name
```
### Example using a per-plugin matrix
@@ -208,26 +207,25 @@ jobs:
test-outcome: ${{ steps.run-tests-latest.outcome }}
# repeat steps but for another Grafana version if necessary
- deploy-pages:
+ deploy-reports:
if: ${{ always() && !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) }}
needs: [e2e]
runs-on: ubuntu-latest
permissions:
- contents: write
+ id-token: write
pull-requests: write
steps:
- - uses: actions/checkout@v4
-
- # use deploy-report-pages Action to deploy the artifacts to GitHub Pages
+ # use deploy-report-pages Action to upload the artifacts to GCS
- name: Publish report
uses: grafana/plugin-actions/playwright-gh-pages/deploy-report-pages@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+ bucket: your-gcs-bucket-name
```
## Inputs
-For details on what the available inputs for the Actions, refer to the [README](./deploy-report-pages/README.md) of `deploy-report-pages` and the [README](./upload-report-artifacts/README.md) of `upload-report-artifacts`
+For details on available inputs for the Actions, refer to the [README](./deploy-report-pages/README.md) of `deploy-report-pages` and the [README](./upload-report-artifacts/README.md) of `upload-report-artifacts`.
If you want to skip publishing for forked pull requests while still allowing scheduled runs, pushes, and same-repository pull requests, use this condition on the publish job:
diff --git a/playwright-gh-pages/deploy-report-pages/README.md b/playwright-gh-pages/deploy-report-pages/README.md
index 372a9104..0c0a3167 100644
--- a/playwright-gh-pages/deploy-report-pages/README.md
+++ b/playwright-gh-pages/deploy-report-pages/README.md
@@ -1,17 +1,16 @@
-# Publish to GitHub Pages Action
+# Deploy Playwright Reports to GCS Action
-This GitHub Action automates the process of publishing test artifacts to GitHub Pages and commenting on the pull request with the results and links. It is designed to work together with the `upload-report-artifacts` action.
+This GitHub Action uploads Playwright test reports to Google Cloud Storage and comments on the pull request with the results and links. It is designed to work together with the `upload-report-artifacts` action.
## Usage
-See full blown examples [here](../README.md).
+See full examples [here](../README.md).
## Inputs
-| Input Name | Description | Required | Default |
-| -------------------- | ------------------------------------------------------------------------------ | -------- | ------------------------ |
-| `github-token` | Token for the repository. Pass `${{ secrets.GITHUB_TOKEN }}` from the publish job. | Yes | N/A |
-| `retention-days` | Number of days to retain the reports. | Yes | 30 |
-| `pr-comment-summary` | Whether to manage a PR comment with the test results. Set this to `false` if you do not want the action to create or delete PR comments. | Yes | true |
-| `artifact-pattern` | Pattern to match the artifacts. | Yes | `gf-playwright-report-*` |
-| `pages-branch` | Branch to deploy the reports to. | Yes | `gh-pages` |
+| Input Name | Description | Required | Default |
+| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------ |
+| `github-token` | Token for the repository. Pass `${{ secrets.GITHUB_TOKEN }}` from the publish job. | Yes | N/A |
+| `pr-comment-summary` | Whether to manage a PR comment with the test results. Set this to `false` if you do not want the action to create or delete PR comments. | Yes | `true` |
+| `artifact-pattern` | Pattern to match the artifacts. | Yes | `gf-playwright-report-*` |
+| `bucket` | GCS bucket name to upload reports to. | Yes | N/A |
diff --git a/playwright-gh-pages/deploy-report-pages/action.yml b/playwright-gh-pages/deploy-report-pages/action.yml
index c6688ef7..fb5db1f5 100644
--- a/playwright-gh-pages/deploy-report-pages/action.yml
+++ b/playwright-gh-pages/deploy-report-pages/action.yml
@@ -1,14 +1,10 @@
-name: 'Deploy to GH pages'
-description: 'Deploys test artifacts to GitHub Pages and comments on the PR with the results and links.'
+name: 'Deploy test reports'
+description: 'Uploads Playwright test reports to Google Cloud Storage and comments on the PR with the results and links.'
inputs:
github-token:
description: 'Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`.'
required: true
- retention-days:
- description: 'Number of days to retain the reports. Default is 30.'
- required: true
- default: 30
pr-comment-summary:
description: 'Whether to comment the PR with the test results. Default is true.'
required: true
@@ -17,10 +13,9 @@ inputs:
description: 'Pattern to match the artifacts. Default is "gf-playwright-report-*"'
required: true
default: 'gf-playwright-report-*'
- pages-branch:
- description: 'Branch to deploy the reports to. Default is "gh-pages".'
+ bucket:
+ description: 'GCS bucket name to upload reports to.'
required: true
- default: 'gh-pages'
runs:
using: "composite"
@@ -54,6 +49,24 @@ runs:
echo "job_initiator=${{ github.run_id }}" >> "$GITHUB_OUTPUT"
fi
+ - name: Login to GCS
+ id: login-to-gcs
+ uses: grafana/shared-workflows/actions/login-to-gcs@6a97ef28077dbb66da3582aad604aef980cd1787 # login-to-gcs/v0.3.0
+ with:
+ bucket: ${{ inputs.bucket }}
+ use_wif_auth: 'true'
+ delete_credentials_file: 'false'
+
+ - name: Upload reports to GCS
+ uses: google-github-actions/upload-cloud-storage@6397bd7208e18d13ba2619ee21b9873edc94427a # v3.0.0
+ with:
+ path: all-reports
+ destination: ${{ steps.login-to-gcs.outputs.bucket }}/${{ github.repository }}/${{ steps.timestampid.outputs.timestamp }}/${{ steps.set-initiator.outputs.job_initiator }}
+ parent: false
+ predefinedAcl: ' '
+ gzip: false
+ process_gcloudignore: false
+
- name: Generate Playwright Test Results Table
id: generate-table
run: |
@@ -69,9 +82,8 @@ runs:
env:
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
- TIMESTAMP: ${{ steps.timestampid.outputs.timestamp }}
- JOB_INITIATOR: ${{ steps.set-initiator.outputs.job_initiator }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ REPORT_BASE_URL: https://storage.cloud.google.com/${{ steps.login-to-gcs.outputs.bucket }}/${{ github.repository }}/${{ steps.timestampid.outputs.timestamp }}/${{ steps.set-initiator.outputs.job_initiator }}
- name: Write result table in Action summary
shell: bash
@@ -87,53 +99,3 @@ runs:
create-if-not-exists: true
comment-tag: gf-playwright-test-results
message: ${{ steps.generate-table.outputs.table }}
-
- - name: Validate pages branch safety
- shell: bash
- run: |
- if [[ "$PAGES_BRANCH" == "main" || "$PAGES_BRANCH" == "master" ]]; then
- echo "ERROR: Cannot deploy to main/master branch"
- echo "Please use a dedicated branch like 'gh-pages' for Pages deployment."
- exit 1
- fi
- env:
- PAGES_BRANCH: ${{ inputs.pages-branch }}
-
- - name: Push the new files to github pages
- uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
- with:
- publish_branch: ${{ inputs.pages-branch }}
- github_token: ${{ inputs.github-token }}
- publish_dir: all-reports
- destination_dir: ${{ steps.timestampid.outputs.timestamp }}/${{ steps.set-initiator.outputs.job_initiator }}
- force_orphan: true
-
-
- - name: Checkout code
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- with:
- ref: ${{ inputs.pages-branch }}
-
- - name: Delete old reports
- shell: bash
- run: ${{ github.action_path }}/cleanup-folders.sh --retention-days $RETENTION_DAYS --folder-name .
- env:
- RETENTION_DAYS: ${{ inputs.retention-days }}
-
- - name: Commit and push changed files
- shell: bash
- run: |
- git config --local user.name grafana-plugins-platform-bot[bot]
- git config --local user.email 144369747+grafana-plugins-platform-bot[bot]@users.noreply.github.com
-
- git add -A
- if git diff --staged --quiet; then
- echo "No changes to commit"
- else
- git commit -m "Delete folders older than $RETENTION_DAYS days"
- git push -f origin $PAGES_BRANCH
- fi
- env:
- RETENTION_DAYS: ${{ inputs.retention-days }}
- PAGES_BRANCH: ${{ inputs.pages-branch }}
-
diff --git a/playwright-gh-pages/deploy-report-pages/build-pr-comment.js b/playwright-gh-pages/deploy-report-pages/build-pr-comment.js
index 63a47478..3dc16db1 100644
--- a/playwright-gh-pages/deploy-report-pages/build-pr-comment.js
+++ b/playwright-gh-pages/deploy-report-pages/build-pr-comment.js
@@ -1,22 +1,6 @@
const fs = require('fs');
const path = require('path');
-const troubleshootingSection = `\n
-
- Troubleshooting
-
-### 404 when clicking on \`View report\`
-
-By default, the \`deploy-report-pages\` Action deploys reports to the \`gh-pages\` branch. However, **you need to take an extra step** to ensure that GitHub Pages can build and serve the site from this branch. To do so:
-
-1. Go to the **Settings** tab of your repository.
-2. In the left-hand sidebar, click on **Pages**.
-3. Under **Source**, select **Deploy from a branch**, then choose the gh-pages branch.
-
-This action needs to be completed **manually** in order for your GitHub Pages site to be built and accessible from the \`gh-pages\` branch. Once configured, GitHub will automatically build and serve the site whenever new reports are deployed.
-
- `;
-
async function buildPrComment() {
// Ensure we are in the right directory
const reportsDir = 'all-reports';
@@ -51,6 +35,12 @@ async function buildPrComment() {
let rows = [];
let uploadReportDisabled = false;
+ const reportBaseUrl = process.env.REPORT_BASE_URL;
+ if (!reportBaseUrl) {
+ console.error('REPORT_BASE_URL environment variable is required but was not set.');
+ process.exit(1);
+ }
+
// Iterate through subdirectories
fs.readdirSync(reportsDir).forEach((dir) => {
const dirPath = path.join(reportsDir, dir);
@@ -73,14 +63,7 @@ async function buildPrComment() {
uploadReportDisabled = getValue('UPLOAD_REPORT_ENABLED') === 'false';
// Construct report link
- const repoOwner = process.env.GITHUB_REPOSITORY_OWNER;
- const repoName = process.env.GITHUB_REPOSITORY_NAME;
- const timestamp = process.env.TIMESTAMP;
- const jobInitiator = process.env.JOB_INITIATOR;
-
- const reportLink = pluginName
- ? `https://${repoOwner}.github.io/${repoName}/${timestamp}/${jobInitiator}/${pluginName}-${grafanaImage}-${grafanaVersion}/`
- : `https://${repoOwner}.github.io/${repoName}/${timestamp}/${jobInitiator}/${grafanaImage}-${grafanaVersion}/`;
+ const reportLink = `${reportBaseUrl}/${dir}/index.html`;
// Map result to emoji
const resultEmoji = testOutput === 'success' ? '✅' : '❌';
@@ -108,12 +91,11 @@ async function buildPrComment() {
const ciLink = `https://github.com/${process.env.GITHUB_REPOSITORY_OWNER}/${process.env.GITHUB_REPOSITORY_NAME}/blob/${process.env.DEFAULT_BRANCH}/.github/workflows/ci.yml`;
if (uploadReportDisabled) {
- table += `
- \n ⚠️ To make Playwright reports for failed tests publicly accessible on GitHub Pages, set the \`upload-report\` input to \`true\` in your [CI workflow](${ciLink}). For more details, refer to the [Developer Portal documentation](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci).\n`;
- } else {
- table += troubleshootingSection;
+ table += `\n⚠️ To make Playwright reports for failed tests accessible, set the \`upload-report\` input to \`true\` in your [CI workflow](${ciLink}). For more details, refer to the [Developer Portal documentation](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci).\n`;
}
+ table += `\n> ℹ️ Reports require a Grafana Google Workspace sign-in to view and are retained for 90 days.`;
+
console.log(table);
}
diff --git a/playwright-gh-pages/deploy-report-pages/cleanup-folders.sh b/playwright-gh-pages/deploy-report-pages/cleanup-folders.sh
deleted file mode 100755
index 54698262..00000000
--- a/playwright-gh-pages/deploy-report-pages/cleanup-folders.sh
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/bin/bash
-
-log_skipped() {
- echo "SKIPPED --- $1" >&2
-}
-
-delete_folder() {
- local folder_path="$1"
- if rm -rf "$folder_path"; then
- echo "DELETED --- Folder '$folder_path' and its contents have been deleted."
- else
- echo "ERROR --- Failed to delete folder '$folder_path'."
- fi
-}
-
-# parses and validates a folder name as a date
-parse_folder_date() {
- local folder_name="$1"
- if [[ "$folder_name" =~ ^[0-9]{8}$ ]]; then
- echo "${folder_name:0:4}-${folder_name:4:2}-${folder_name:6:2}" # Convert to "YYYY-MM-DD"
- else
- echo ""
- fi
-}
-
-# calculates the age of a folder in days
-calculate_folder_age() {
- local folder_date="$1"
- local current_date
- current_date=$(date -u +%s)
- local folder_date_epoch
- folder_date_epoch=$(date -u -d "$folder_date" +%s 2>/dev/null || echo "")
- if [[ -n "$folder_date_epoch" ]]; then
- echo $(( (current_date - folder_date_epoch) / 86400 )) # Age in days
- else
- echo ""
- fi
-}
-
-find_old_folders() {
- local retention_days="$1"
- local directory="$2"
-
- echo "Checking folder..." >&2
- for folder in "$directory"/*/; do
- folder_name=$(basename "$folder")
- folder_date=$(parse_folder_date "$folder_name")
-
- if [[ -n "$folder_date" ]]; then
- age_days=$(calculate_folder_age "$folder_date")
- if [[ -n "$age_days" && $age_days -gt $retention_days ]]; then
- echo "$folder_name" # eligible for deletion
- else
- log_skipped "Folder '$folder_name' is not older than $retention_days days. It will not be deleted."
- fi
- else
- log_skipped "Found folder/file with name '$folder_name' that does not match the expected date format. It will not be deleted."
- fi
- done
-}
-
-# validates and processes arguments
-parse_arguments() {
- local retention_days=""
- local folder_name=""
-
- while [[ $# -gt 0 ]]; do
- case "$1" in
- --retention-days)
- retention_days="$2"
- shift 2
- ;;
- --folder-name)
- folder_name="$2"
- shift 2
- ;;
- *)
- echo "Usage: $0 --retention-days --folder-name "
- exit 1
- ;;
- esac
- done
-
- if [[ -z "$retention_days" || -z "$folder_name" ]]; then
- echo "Usage: $0 --retention-days --folder-name "
- exit 1
- fi
-
- if [[ ! -d "$folder_name" ]]; then
- echo "Error: Directory '$folder_name' does not exist."
- exit 1
- fi
-
- echo "$retention_days" "$folder_name"
-}
-
-main() {
- read -r retention_days folder_name <<< "$(parse_arguments "$@")"
-
- # find old folders and delete them
- old_folders=$(find_old_folders "$retention_days" "$folder_name")
- echo "Old folders found: $old_folders" >&2
- for folder in $old_folders; do
- delete_folder "$folder_name/$folder"
- done
-}
-
-# run the script
-main "$@"