diff --git a/.github/workflows/e2e-testing.yml.example b/.github/workflows/e2e-testing.yml.example new file mode 100644 index 0000000..5f519d6 --- /dev/null +++ b/.github/workflows/e2e-testing.yml.example @@ -0,0 +1,497 @@ +name: E2E Testing Suite (Screenshots & Accessibility) + +concurrency: + group: ${{ github.event_name == 'pull_request' && format('e2e-pr-{0}', github.event.pull_request.number) || format('e2e-{0}', github.ref) }} + cancel-in-progress: false # Don't cancel immediately - wait for timeout + +permissions: + contents: write # needed to push to gh-pages + pages: write # deploy with actions/deploy-pages + id-token: write # required by actions/deploy-pages + pull-requests: write + issues: write + +on: + push: + branches: + - main + pull_request: + +jobs: + e2e-tests: + name: E2E Tests (Screenshots & A11y) + runs-on: ubuntu-latest + env: + PORT: 4173 # Port for preview server and Playwright tests + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Simple notification for PR-specific concurrency + - name: PR Test Info + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.payload.pull_request.number; + core.info(`Starting E2E tests for PR ${prNumber} with isolated concurrency group.`); + core.info(`This PR will publish screenshots to: /(Dev-Shots)/PR-${prNumber}/`); + + - name: Use Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install deps + run: npm ci + + - name: Build site + run: npm run build + + # --- Added steps to start and wait for the preview server --- + - name: Start preview server + run: npm run preview -- --port $PORT & + + - name: Wait for server to be ready + run: npx wait-on http://localhost:$PORT + # ----------------------------------------------------------- + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} + + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + + - name: Run Playwright tests (screenshots & accessibility) + run: | + npx playwright test tests/e2e.spec.ts tests/a11y.spec.ts + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: e2e-test-results + path: | + playwright-logs/portfolio-web-light.png + playwright-logs/portfolio-web-dark.png + playwright-logs/portfolio-mobile-light.png + playwright-logs/portfolio-mobile-dark.png + playwright-report/** + axe-report.json + + - name: Add screenshot to job summary + run: | + web_light="playwright-logs/portfolio-web-light.png" + web_dark="playwright-logs/portfolio-web-dark.png" + mobile_light="playwright-logs/portfolio-mobile-light.png" + mobile_dark="playwright-logs/portfolio-mobile-dark.png" + + has_any=false + if [ -f "$web_light" ] || [ -f "$web_dark" ] || [ -f "$mobile_light" ] || [ -f "$mobile_dark" ]; then + has_any=true + fi + + if [ "$has_any" = true ]; then + { + echo "## Web Render" + if [ -f "$web_light" ] || [ -f "$web_dark" ]; then + echo + echo "### Web" + if [ -f "$web_light" ]; then + echo + echo "#### Light" + echo + echo "![Web Light Render]($web_light)" + fi + if [ -f "$web_dark" ]; then + echo + echo "#### Dark" + echo + echo "![Web Dark Render]($web_dark)" + fi + fi + if [ -f "$mobile_light" ] || [ -f "$mobile_dark" ]; then + echo + echo "### Mobile" + if [ -f "$mobile_light" ]; then + echo + echo "#### Light" + echo + echo "![Mobile Light Render]($mobile_light)" + fi + if [ -f "$mobile_dark" ]; then + echo + echo "#### Dark" + echo + echo "![Mobile Dark Render]($mobile_dark)" + fi + fi + echo + echo "_This screenshot was generated by Playwright during the latest workflow run._" + } >> "$GITHUB_STEP_SUMMARY" + else + echo "⚠️ Screenshots not found." >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Prepare Pages payload and workspace archive + id: prep_pages + run: | + # Use PR-specific directory structure instead of run-based + if [ "${{ github.event_name }}" = "pull_request" ]; then + pr_number="${{ github.event.pull_request.number }}" + pages_root="_screenshot_publish/(Dev-Shots)/PR-${pr_number}" + repo_root="(Dev-Shots)/PR-${pr_number}" + else + # For main branch, use run-based structure + run_dir=${{ github.run_id }} + pages_root="_screenshot_publish/(Dev-Shots)/screenshots/$run_dir" + repo_root="(Dev-Shots)/screenshots/$run_dir" + fi + + rm -rf _screenshot_publish + mkdir -p "$pages_root" + mkdir -p "$repo_root" + + files=( + "portfolio-web-light.png" + "portfolio-web-dark.png" + "portfolio-mobile-light.png" + "portfolio-mobile-dark.png" + ) + + found=false + found_web=false + found_mobile=false + found_web_light=false + found_web_dark=false + found_mobile_light=false + found_mobile_dark=false + + for file in "${files[@]}"; do + src="playwright-logs/$file" + if [ -f "$src" ]; then + cp "$src" "$pages_root/$file" + cp "$src" "$repo_root/$file" + found=true + + case "$file" in + portfolio-web-light.png) + found_web=true + found_web_light=true + ;; + portfolio-web-dark.png) + found_web=true + found_web_dark=true + ;; + portfolio-mobile-light.png) + found_mobile=true + found_mobile_light=true + ;; + portfolio-mobile-dark.png) + found_mobile=true + found_mobile_dark=true + ;; + esac + fi + done + + if [ "$found" = true ]; then + echo "found=true" >> "$GITHUB_OUTPUT" + else + echo "found=false" >> "$GITHUB_OUTPUT" + fi + + if [ "$found_web" = true ]; then + echo "found_web=true" >> "$GITHUB_OUTPUT" + else + echo "found_web=false" >> "$GITHUB_OUTPUT" + fi + + if [ "$found_mobile" = true ]; then + echo "found_mobile=true" >> "$GITHUB_OUTPUT" + else + echo "found_mobile=false" >> "$GITHUB_OUTPUT" + fi + + if [ "$found_web_light" = true ]; then + echo "found_web_light=true" >> "$GITHUB_OUTPUT" + else + echo "found_web_light=false" >> "$GITHUB_OUTPUT" + fi + + if [ "$found_web_dark" = true ]; then + echo "found_web_dark=true" >> "$GITHUB_OUTPUT" + else + echo "found_web_dark=false" >> "$GITHUB_OUTPUT" + fi + + if [ "$found_mobile_light" = true ]; then + echo "found_mobile_light=true" >> "$GITHUB_OUTPUT" + else + echo "found_mobile_light=false" >> "$GITHUB_OUTPUT" + fi + + if [ "$found_mobile_dark" = true ]; then + echo "found_mobile_dark=true" >> "$GITHUB_OUTPUT" + else + echo "found_mobile_dark=false" >> "$GITHUB_OUTPUT" + fi + + # Store the repo root for use in later steps (remove the leading parentheses part) + repo_root_clean="${repo_root#(Dev-Shots)/}" + echo "repo_root=$repo_root_clean" >> "$GITHUB_OUTPUT" + + # Publish screenshot via GitHub Pages for same-repo runs/PRs + - name: Configure GitHub Pages + if: steps.prep_pages.outputs.found == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) + uses: actions/configure-pages@v4 + + - name: Upload screenshot to Pages artifact + if: steps.prep_pages.outputs.found == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) + uses: actions/upload-pages-artifact@v3 + with: + path: _screenshot_publish + name: github-pages-screenshot-${{ github.run_id }} + + - name: Deploy GitHub Pages site + id: deploy + if: steps.prep_pages.outputs.found == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) + uses: actions/deploy-pages@v4 + with: + preview: ${{ github.event_name == 'pull_request' }} + artifact_name: github-pages-screenshot-${{ github.run_id }} + + - name: Add GitHub Pages link to job summary + if: steps.prep_pages.outputs.found == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) + run: | + page_url="${{ steps.deploy.outputs.preview_url }}" + if [ -z "$page_url" ]; then + page_url="${{ steps.deploy.outputs.page_url }}" + fi + + if [ -n "$page_url" ]; then + base_url="${page_url%/}/(Dev-Shots)" + repo_root="${{ steps.prep_pages.outputs.repo_root }}" + web_found="${{ steps.prep_pages.outputs.found_web }}" + web_light_found="${{ steps.prep_pages.outputs.found_web_light }}" + web_dark_found="${{ steps.prep_pages.outputs.found_web_dark }}" + mobile_found="${{ steps.prep_pages.outputs.found_mobile }}" + mobile_light_found="${{ steps.prep_pages.outputs.found_mobile_light }}" + mobile_dark_found="${{ steps.prep_pages.outputs.found_mobile_dark }}" + { + echo "### GitHub Pages Screenshots" + echo + if [ "$web_found" = 'true' ]; then + if [ "$web_light_found" = 'true' ]; then + echo "- [View Web · Light](${base_url}/${repo_root}/portfolio-web-light.png)" + fi + if [ "$web_dark_found" = 'true' ]; then + echo "- [View Web · Dark](${base_url}/${repo_root}/portfolio-web-dark.png)" + fi + fi + if [ "$mobile_found" = 'true' ]; then + if [ "$mobile_light_found" = 'true' ]; then + echo "- [View Mobile · Light](${base_url}/${repo_root}/portfolio-mobile-light.png)" + fi + if [ "$mobile_dark_found" = 'true' ]; then + echo "- [View Mobile · Dark](${base_url}/${repo_root}/portfolio-mobile-dark.png)" + fi + fi + } >> "$GITHUB_STEP_SUMMARY" + fi + + # Comment inline (same-repo PRs only) + - name: Comment on PR with inline screenshot (Pages URL) + if: steps.prep_pages.outputs.found == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + uses: actions/github-script@v7 + env: + PAGE_URL: ${{ steps.deploy.outputs.page_url }} + PREVIEW_URL: ${{ steps.deploy.outputs.preview_url }} + WEB_FOUND: ${{ steps.prep_pages.outputs.found_web }} + MOBILE_FOUND: ${{ steps.prep_pages.outputs.found_mobile }} + WEB_LIGHT_FOUND: ${{ steps.prep_pages.outputs.found_web_light }} + WEB_DARK_FOUND: ${{ steps.prep_pages.outputs.found_web_dark }} + MOBILE_LIGHT_FOUND: ${{ steps.prep_pages.outputs.found_mobile_light }} + MOBILE_DARK_FOUND: ${{ steps.prep_pages.outputs.found_mobile_dark }} + REPO_ROOT: ${{ steps.prep_pages.outputs.repo_root }} + with: + script: | + const prNumber = context.issue.number; + const baseUrl = (process.env.PREVIEW_URL || process.env.PAGE_URL || '').replace(/\/$/, ''); + if (!baseUrl) { + core.info('Pages URL missing; skipping comment.'); + return; + } + const siteBaseUrl = `${baseUrl}/(Dev-Shots)`; + const owner = context.repo.owner; + const repo = context.repo.repo; + const issueNumber = context.issue.number; + const marker = ''; + const commitSha = context.sha.slice(0, 7); + const commitUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${context.sha}`; + const webFound = process.env.WEB_FOUND === 'true'; + const mobileFound = process.env.MOBILE_FOUND === 'true'; + const webLightFound = process.env.WEB_LIGHT_FOUND === 'true'; + const webDarkFound = process.env.WEB_DARK_FOUND === 'true'; + const mobileLightFound = process.env.MOBILE_LIGHT_FOUND === 'true'; + const mobileDarkFound = process.env.MOBILE_DARK_FOUND === 'true'; + const repoRoot = process.env.REPO_ROOT; + + const sections = [ + marker, + '**Web Render**', + '', + `Commit: [\`${commitSha}\`](${commitUrl})`, + '' + ]; + + if (webFound) { + const webLightUrl = `${siteBaseUrl}/${repoRoot}/portfolio-web-light.png`; + const webDarkUrl = `${siteBaseUrl}/${repoRoot}/portfolio-web-dark.png`; + const webContent = []; + + if (webLightFound || webDarkFound) { + webContent.push(''); + if (webLightFound) { + webContent.push( + `` + ); + } + if (webDarkFound) { + webContent.push( + `` + ); + } + webContent.push('
` + + `
Light
` + + `Playwright screenshot of the portfolio (web · light)` + + `
` + + `
Dark
` + + `Playwright screenshot of the portfolio (web · dark)` + + `
', ''); + } else { + webContent.push('_Web render unavailable for this run._', ''); + } + + sections.push('
', 'View Web', '', ...webContent, '
', ''); + } + + if (mobileFound) { + const mobileLightUrl = `${siteBaseUrl}/${repoRoot}/portfolio-mobile-light.png`; + const mobileDarkUrl = `${siteBaseUrl}/${repoRoot}/portfolio-mobile-dark.png`; + const mobileContent = []; + + if (mobileLightFound || mobileDarkFound) { + mobileContent.push(''); + if (mobileLightFound) { + mobileContent.push( + `` + ); + } + if (mobileDarkFound) { + mobileContent.push( + `` + ); + } + mobileContent.push('
` + + `
Light
` + + `Playwright screenshot of the portfolio (mobile · light)` + + `
` + + `
Dark
` + + `Playwright screenshot of the portfolio (mobile · dark)` + + `
', ''); + } else { + mobileContent.push('_Mobile render unavailable for this run._', ''); + } + + sections.push('
', 'View Mobile', '', ...mobileContent, '
', ''); + } + + if (!webFound && !mobileFound) { + sections.push('_No screenshots available for this run._', ''); + } + + sections.push('_This screenshot was generated by Playwright during the latest workflow run._'); + + const body = sections.join('\n'); + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issueNumber, + }); + + // Check for existing screenshot comments + const existing = comments.find((comment) => comment.body && comment.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body, + }); + } + + # Fallback for forked PRs (can't push to gh-pages) + - name: Comment on PR (fork fallback) + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true + uses: actions/github-script@v7 + with: + script: | + const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const marker = ''; + const commitSha = context.sha.slice(0, 7); + const commitUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${context.sha}`; + const body = [ + marker, + '**Web Render**', + '', + `Commit: [\`${commitSha}\`](${commitUrl})`, + '', + 'Inline image is unavailable for forked PRs due to token restrictions.', + '', + `Please view the Light & Dark captures in the [workflow run summary](${runUrl}).`, + '', + '_This screenshot was generated by Playwright during the latest workflow run._' + ].join('\n'); + + const owner = context.repo.owner; + const repo = context.repo.repo; + const issueNumber = context.issue.number; + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issueNumber, + }); + + const existing = comments.find((comment) => comment.body && comment.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body, + }); + } diff --git a/README.md b/README.md index fd61f75..666a346 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,391 @@ -# E2E -Github Action that takes a screenshot of your website PR in both mobile and dark mode +# E2E Testing Suite (Screenshots & Accessibility) + +A comprehensive GitHub Action that automatically captures screenshots of your website in multiple modes (web/mobile, light/dark) and performs accessibility testing using Playwright. + +## 🌟 Features + +- **📸 Automatic Screenshot Capture**: Takes screenshots in web and mobile viewports +- **🌓 Light & Dark Mode Support**: Captures both light and dark theme variations +- **♿ Accessibility Testing**: Runs Axe accessibility tests to ensure WCAG compliance +- **📊 GitHub Pages Integration**: Publishes screenshots to GitHub Pages with preview URLs +- **💬 PR Comments**: Automatically posts screenshots directly in pull request comments +- **🔄 Concurrency Control**: Smart concurrency management for PRs and main branch +- **🍴 Fork-Friendly**: Special handling for forked repository PRs +- **📦 Artifact Archiving**: Uploads test results and reports as workflow artifacts + +## 🚀 Usage + +### Option 1: Use as a GitHub Action (Recommended) + +Add this to your workflow file (`.github/workflows/e2e.yml`): + +```yaml +name: E2E Tests + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run E2E Tests + uses: SillyLittleTech/E2E@v1 + with: + port: '4173' + node-version: '20' + build-command: 'npm run build' + preview-command: 'npm run preview' +``` + +### Option 2: Copy the Complete Workflow + +### Option 2: Copy the Complete Workflow + +For more control and GitHub Pages integration, copy the full workflow file to your repository: + +1. **Copy the example workflow file** from this repository to yours: + ```bash + # Copy .github/workflows/e2e-testing.yml.example to your repository as: + .github/workflows/e2e-testing.yml + ``` + +2. **Set up required dependencies** in your project: + ```bash + npm install --save-dev playwright @axe-core/playwright wait-on + ``` + +3. **Create Playwright test files** in your repository: + - `tests/e2e.spec.ts` - For screenshot capture tests + - `tests/a11y.spec.ts` - For accessibility tests + +4. **Configure your `package.json`** with required scripts: + ```json + { + "scripts": { + "build": "your-build-command", + "preview": "your-preview-server-command" + } + } + ``` + +5. **Enable GitHub Pages** in your repository settings: + - Go to Settings > Pages + - Source: GitHub Actions + +### Prerequisites + +Before using this action, ensure your project has: + +- **Node.js project** with `package.json` +- **Build script** defined (e.g., `npm run build`) +- **Preview server script** defined (e.g., `npm run preview`) +- **Playwright tests** in your repository + +Install required dependencies: +```bash +npm install --save-dev playwright @axe-core/playwright wait-on +``` + +## 📋 Action Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `port` | Port for the preview server and Playwright tests | No | `4173` | +| `node-version` | Node.js version to use | No | `20` | +| `build-command` | Command to build the site | No | `npm run build` | +| `preview-command` | Command to start the preview server | No | `npm run preview` | +| `test-files` | Playwright test files to run (space-separated) | No | `tests/e2e.spec.ts tests/a11y.spec.ts` | +| `screenshot-prefix` | Prefix for screenshot filenames | No | `portfolio` | + +### Example with Custom Inputs + +```yaml +- name: Run E2E Tests + uses: SillyLittleTech/E2E@v1 + with: + port: '3000' + node-version: '18' + build-command: 'npm run build:prod' + preview-command: 'npm run serve' + test-files: 'tests/*.spec.ts' + screenshot-prefix: 'my-app' +``` + +## 📝 Example Playwright Test Files + +#### `tests/e2e.spec.ts` (Screenshot Capture) +```typescript +import { test, expect } from '@playwright/test'; + +test.describe('Screenshot Capture', () => { + test('capture web light mode', async ({ page }) => { + await page.goto('http://localhost:4173'); + await page.emulateMedia({ colorScheme: 'light' }); + await page.screenshot({ + path: 'playwright-logs/portfolio-web-light.png', + fullPage: true + }); + }); + + test('capture web dark mode', async ({ page }) => { + await page.goto('http://localhost:4173'); + await page.emulateMedia({ colorScheme: 'dark' }); + await page.screenshot({ + path: 'playwright-logs/portfolio-web-dark.png', + fullPage: true + }); + }); + + test('capture mobile light mode', async ({ page }) => { + await page.setViewportSize({ width: 375, height: 667 }); + await page.goto('http://localhost:4173'); + await page.emulateMedia({ colorScheme: 'light' }); + await page.screenshot({ + path: 'playwright-logs/portfolio-mobile-light.png', + fullPage: true + }); + }); + + test('capture mobile dark mode', async ({ page }) => { + await page.setViewportSize({ width: 375, height: 667 }); + await page.goto('http://localhost:4173'); + await page.emulateMedia({ colorScheme: 'dark' }); + await page.screenshot({ + path: 'playwright-logs/portfolio-mobile-dark.png', + fullPage: true + }); + }); +}); +``` + +#### `tests/a11y.spec.ts` (Accessibility Testing) +```typescript +import { test, expect } from '@playwright/test'; +import AxeBuilder from '@axe-core/playwright'; +import fs from 'fs'; + +test.describe('Accessibility Tests', () => { + test('should not have any automatically detectable accessibility issues', async ({ page }) => { + await page.goto('http://localhost:4173'); + + const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); + + // Save report + fs.writeFileSync('axe-report.json', JSON.stringify(accessibilityScanResults, null, 2)); + + expect(accessibilityScanResults.violations).toEqual([]); + }); +}); +``` + +## ⚙️ Configuration + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `4173` | Port for the preview server and Playwright tests | + +You can customize the port by modifying the `env` section in the workflow: + +```yaml +env: + PORT: 4173 # Change to your preferred port +``` + +### Workflow Triggers + +The workflow is triggered on: +- **Push to main branch**: Runs tests and publishes to GitHub Pages +- **Pull requests**: Runs tests, posts screenshots as PR comments + +```yaml +on: + push: + branches: + - main + pull_request: +``` + +### Required Permissions + +The workflow requires the following permissions: + +```yaml +permissions: + contents: write # needed to push to gh-pages + pages: write # deploy with actions/deploy-pages + id-token: write # required by actions/deploy-pages + pull-requests: write # comment on PRs + issues: write # comment on issues +``` + +### Concurrency Groups + +The workflow uses smart concurrency management: + +- **Pull Requests**: Each PR gets its own concurrency group (`e2e-pr-{PR_NUMBER}`) +- **Main Branch**: Uses the branch ref as concurrency group (`e2e-{REF}`) +- **Cancel in Progress**: Disabled to allow tests to complete + +```yaml +concurrency: + group: ${{ github.event_name == 'pull_request' && format('e2e-pr-{0}', github.event.pull_request.number) || format('e2e-{0}', github.ref) }} + cancel-in-progress: false +``` + +## 📁 Output Structure + +### Screenshot Locations + +Screenshots are saved in the following structure: + +**For Pull Requests:** +``` +/(Dev-Shots)/PR-{PR_NUMBER}/ + ├── portfolio-web-light.png + ├── portfolio-web-dark.png + ├── portfolio-mobile-light.png + └── portfolio-mobile-dark.png +``` + +**For Main Branch:** +``` +/(Dev-Shots)/screenshots/{RUN_ID}/ + ├── portfolio-web-light.png + ├── portfolio-web-dark.png + ├── portfolio-mobile-light.png + └── portfolio-mobile-dark.png +``` + +### Artifacts + +The workflow uploads the following artifacts: + +- `e2e-test-results` containing: + - All screenshot PNG files + - Playwright HTML report (`playwright-report/**`) + - Axe accessibility report (`axe-report.json`) + +## 📝 Workflow Behavior + +### For Pull Requests (Same Repository) + +1. Runs E2E and accessibility tests +2. Captures screenshots in all modes +3. Publishes screenshots to GitHub Pages with preview URL +4. Posts an inline comment on the PR with embedded screenshots +5. Updates the comment on subsequent commits + +### For Pull Requests (Forked Repository) + +1. Runs E2E and accessibility tests +2. Captures screenshots in all modes +3. Uploads screenshots as workflow artifacts +4. Posts a comment with link to workflow run summary +5. ⚠️ **Note**: Cannot publish to GitHub Pages due to token restrictions + +### For Main Branch Pushes + +1. Runs E2E and accessibility tests +2. Captures screenshots in all modes +3. Publishes screenshots to GitHub Pages +4. Adds links to job summary + +## 🎯 Customization + +### Changing Screenshot Names + +Modify the file names in the workflow: + +```yaml +- name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: e2e-test-results + path: | + playwright-logs/your-custom-name-light.png + playwright-logs/your-custom-name-dark.png +``` + +### Changing Node Version + +Update the Node.js version: + +```yaml +- name: Use Node + uses: actions/setup-node@v4 + with: + node-version: '20' # Change to your preferred version +``` + +### Adding Custom Test Files + +Add more test files to the Playwright test command: + +```yaml +- name: Run Playwright tests + run: | + npx playwright test tests/e2e.spec.ts tests/a11y.spec.ts tests/custom.spec.ts +``` + +### Customizing Build and Preview Commands + +Update the commands to match your project: + +```yaml +- name: Build site + run: npm run build # Change to your build command + +- name: Start preview server + run: npm run preview -- --port $PORT & # Change to your preview command +``` + +## 🔍 Troubleshooting + +### Screenshots Not Generated + +- Ensure your Playwright tests save screenshots to `playwright-logs/` directory +- Check that the file names match the expected pattern +- Verify the preview server is running on the correct port + +### Preview Server Not Starting + +- Ensure your `package.json` has a `preview` script +- Check that the port is not already in use +- Verify the build output is in the correct location + +### Accessibility Tests Failing + +- Review the `axe-report.json` artifact for violation details +- Fix accessibility issues in your application +- Temporarily adjust Axe rules if needed (but fix issues ASAP) + +### PR Comments Not Appearing + +- Verify workflow has `pull-requests: write` permission +- Check that the workflow completed successfully +- For forked PRs, comments are limited - check workflow artifacts + +### GitHub Pages Not Deploying + +- Enable GitHub Pages in repository settings +- Ensure workflow has required permissions (`pages: write`, `id-token: write`) +- Check the Pages settings are set to "GitHub Actions" as source + +## 📚 Additional Resources + +- [Playwright Documentation](https://playwright.dev) +- [Axe Core Playwright Integration](https://github.com/dequelabs/axe-core-npm/tree/develop/packages/playwright) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [GitHub Pages Documentation](https://docs.github.com/en/pages) + +## 📄 License + +See [LICENSE](LICENSE) file for details. diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..4b1bde6 --- /dev/null +++ b/action.yml @@ -0,0 +1,141 @@ +name: 'E2E Testing Suite' +description: 'Automatically capture screenshots of your website in web/mobile and light/dark modes, plus accessibility testing with Playwright' +author: 'SillyLittleTech' + +branding: + icon: 'camera' + color: 'purple' + +inputs: + port: + description: 'Port for the preview server and Playwright tests' + required: false + default: '4173' + node-version: + description: 'Node.js version to use' + required: false + default: '20' + build-command: + description: 'Command to build the site' + required: false + default: 'npm run build' + preview-command: + description: 'Command to start the preview server' + required: false + default: 'npm run preview' + test-files: + description: 'Playwright test files to run (space-separated)' + required: false + default: 'tests/e2e.spec.ts tests/a11y.spec.ts' + screenshot-prefix: + description: 'Prefix for screenshot filenames' + required: false + default: 'portfolio' + +runs: + using: 'composite' + steps: + - name: Use Node + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + + - name: Install deps + shell: bash + run: npm ci + + - name: Build site + shell: bash + run: ${{ inputs.build-command }} + + - name: Start preview server + shell: bash + run: ${{ inputs.preview-command }} -- --port ${{ inputs.port }} & + + - name: Wait for server to be ready + shell: bash + run: npx wait-on http://localhost:${{ inputs.port }} + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} + + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + shell: bash + run: npx playwright install --with-deps + + - name: Run Playwright tests (screenshots & accessibility) + shell: bash + run: | + npx playwright test ${{ inputs.test-files }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: e2e-test-results + path: | + playwright-logs/${{ inputs.screenshot-prefix }}-web-light.png + playwright-logs/${{ inputs.screenshot-prefix }}-web-dark.png + playwright-logs/${{ inputs.screenshot-prefix }}-mobile-light.png + playwright-logs/${{ inputs.screenshot-prefix }}-mobile-dark.png + playwright-report/** + axe-report.json + + - name: Add screenshot to job summary + shell: bash + run: | + web_light="playwright-logs/${{ inputs.screenshot-prefix }}-web-light.png" + web_dark="playwright-logs/${{ inputs.screenshot-prefix }}-web-dark.png" + mobile_light="playwright-logs/${{ inputs.screenshot-prefix }}-mobile-light.png" + mobile_dark="playwright-logs/${{ inputs.screenshot-prefix }}-mobile-dark.png" + + has_any=false + if [ -f "$web_light" ] || [ -f "$web_dark" ] || [ -f "$mobile_light" ] || [ -f "$mobile_dark" ]; then + has_any=true + fi + + if [ "$has_any" = true ]; then + { + echo "## Web Render" + if [ -f "$web_light" ] || [ -f "$web_dark" ]; then + echo + echo "### Web" + if [ -f "$web_light" ]; then + echo + echo "#### Light" + echo + echo "![Web Light Render]($web_light)" + fi + if [ -f "$web_dark" ]; then + echo + echo "#### Dark" + echo + echo "![Web Dark Render]($web_dark)" + fi + fi + if [ -f "$mobile_light" ] || [ -f "$mobile_dark" ]; then + echo + echo "### Mobile" + if [ -f "$mobile_light" ]; then + echo + echo "#### Light" + echo + echo "![Mobile Light Render]($mobile_light)" + fi + if [ -f "$mobile_dark" ]; then + echo + echo "#### Dark" + echo + echo "![Mobile Dark Render]($mobile_dark)" + fi + fi + echo + echo "_This screenshot was generated by Playwright during the latest workflow run._" + } >> "$GITHUB_STEP_SUMMARY" + else + echo "⚠️ Screenshots not found." >> "$GITHUB_STEP_SUMMARY" + fi