diff --git a/.eslintrc.json b/.eslintrc.json index 98ce4f20..243b2733 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,19 @@ { - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "extends": "eslint:recommended", + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], "parserOptions": { "ecmaVersion": "latest", - "sourceType": "module" + "sourceType": "module", + "project": "./tsconfig.json" // Make sure this path is correct if you have a tsconfig.json }, - "rules": {} + "rules": { + // Your custom rules here + } } diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d190efae..9b5ce79f 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -9,241 +9,384 @@ on: jobs: test: runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write + contents: write # Needed for git operations (checkout, peaceiris to push to gh-pages) + pull-requests: write # Needed for sticky PR comment steps: - #--------------------------------------------------- - # 0 – Checkout - #--------------------------------------------------- - - name: Checkout code - uses: actions/checkout@v3 - with: { fetch-depth: 0 } - - #--------------------------------------------------- - # 1 – reviewdog CLI - #--------------------------------------------------- - - name: Setup reviewdog - uses: reviewdog/action-setup@v1 - with: { reviewdog_version: latest } - - #--------------------------------------------------- - # 2 – Prettier → inline review comments - #--------------------------------------------------- - - name: Prettier style check (reviewdog) - shell: bash - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - npx prettier --write '**/*.{js,ts,tsx,jsx,json,yml,yaml,md}' - git diff -U0 --no-color > prettier.patch || true - if [ -s prettier.patch ]; then - cat prettier.patch | reviewdog -f=diff \ - -name="prettier" \ - -reporter=github-pr-review \ - -filter-mode=diff_context \ - -level=warning - else - echo "No Prettier issues found." - fi - - #--------------------------------------------------- - # 3 – Node & deps - #--------------------------------------------------- - - name: Set up Node.js - uses: actions/setup-node@v4 - with: { node-version: 18 } - - - name: Install dependencies - run: npm install - - #--------------------------------------------------- - # 4 – Install Playwright browsers - #--------------------------------------------------- - - name: Install Playwright and browsers - run: npx playwright install --with-deps - - #--------------------------------------------------- - # 5 – Run Playwright tests - #--------------------------------------------------- - - name: Run Playwright tests - run: npx playwright test - - #--------------------------------------------------- - # 6 – Upload HTML report - #--------------------------------------------------- - - name: Upload HTML report - if: always() - uses: actions/upload-artifact@v4 - with: - name: playwright-report - path: playwright-report/ - - #--------------------------------------------------- - # 7 – Extract Playwright test summary - #--------------------------------------------------- - - name: Extract Playwright summary - id: summary - shell: bash - run: | - REPORT="playwright-metrics.json" - - if [ ! -f "$REPORT" ]; then - echo "$REPORT not found!" - exit 1 - fi - - TOTAL=$(jq '[..|objects|select(has("status"))] | length' "$REPORT") - PASSED=$(jq '[..|objects|select(.status?=="expected")] | length' "$REPORT") - FAILED=$(jq '[..|objects|select(.status?=="failed")] | length' "$REPORT") - SKIPPED=$(jq '[..|objects|select(.status?=="skipped")] | length' "$REPORT") - DURATION=$(jq '.stats.duration*1000|floor' "$REPORT") - - if [ "$TOTAL" -eq 0 ]; then - PASS_RATE=0.00 - else - PASS_RATE=$(awk "BEGIN{printf \"%.2f\", ($PASSED/$TOTAL)*100}") - fi - - echo "total=$TOTAL" >> "$GITHUB_OUTPUT" - echo "passed=$PASSED" >> "$GITHUB_OUTPUT" - echo "failed=$FAILED" >> "$GITHUB_OUTPUT" - echo "skipped=$SKIPPED" >> "$GITHUB_OUTPUT" - echo "duration=$DURATION" >> "$GITHUB_OUTPUT" - echo "passrate=$PASS_RATE" >> "$GITHUB_OUTPUT" - - - #--------------------------------------------------- - # 8 – ESLint (tests only) - #--------------------------------------------------- - - name: Run ESLint on GUI tests - shell: bash - run: | - npx eslint "tests/**/*.{js,ts,tsx}" -f json -o eslint-tests.json || true - - - name: Upload ESLint report - if: always() - uses: actions/upload-artifact@v4 - with: - name: eslint-test-report - path: eslint-tests.json - - - name: Read ESLint report (preview) - id: lint_summary - run: | - echo 'summary<> $GITHUB_OUTPUT - jq '.' eslint-tests.json | head -n 20 >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - - #--------------------------------------------------- - # 9 – Extract ESLint test summary - #--------------------------------------------------- - - name: Extract ESLint summary - id: eslint_summary - run: | - REPORT="eslint-tests.json" - TOTAL_FILES=$(jq length "$REPORT") - ERRORS=$(jq '[.[] | .errorCount] | add' "$REPORT") - WARNINGS=$(jq '[.[] | .warningCount] | add' "$REPORT") - FIXABLE_ERRORS=$(jq '[.[] | .fixableErrorCount] | add' "$REPORT") - FIXABLE_WARNINGS=$(jq '[.[] | .fixableWarningCount] | add' "$REPORT") - - echo "total_files=$TOTAL_FILES" >> "$GITHUB_OUTPUT" - echo "errors=$ERRORS" >> "$GITHUB_OUTPUT" - echo "warnings=$WARNINGS" >> "$GITHUB_OUTPUT" - echo "fixable_errors=$FIXABLE_ERRORS" >> "$GITHUB_OUTPUT" - echo "fixable_warnings=$FIXABLE_WARNINGS" >> "$GITHUB_OUTPUT" - - - #--------------------------------------------------- - # 10 – Generate Suite→Spec Mermaid chart (flowchart.png) - #--------------------------------------------------- - - name: Generate test-flow chart - shell: bash - run: | - REPORT="playwright-metrics.json" - set -e - echo "graph TD" > flowchart.mmd - - jq -r ' - .suites[] as $file | + #--------------------------------------------------- + # 0 – Checkout + #--------------------------------------------------- + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + # For PRs, explicitly checkout the PR's head branch to ensure latest changes and context + ref: ${{ github.event.pull_request.head.ref }} + + #--------------------------------------------------- + # 1 – reviewdog CLI (for Prettier) + #--------------------------------------------------- + - name: Setup reviewdog + uses: reviewdog/action-setup@v1 + with: { reviewdog_version: latest } + + #--------------------------------------------------- + # 2 – Prettier → inline review comments + #--------------------------------------------------- + - name: Prettier style check (reviewdog) + shell: bash + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Run Prettier --write to fix issues, suppress error with || true + npx prettier --write '**/*.{js,ts,tsx,jsx,json,yml,yaml,md}' || true + # Generate a diff if Prettier made changes + git diff -U0 --no-color > prettier.patch || true + if [ -s prettier.patch ]; then + cat prettier.patch | reviewdog -f=diff \ + -name="prettier" \ + -reporter=github-pr-review \ + -filter-mode=diff_context \ + -level=warning + else + echo "No Prettier issues found." + fi + + # Discard Prettier changes so they don't affect subsequent git operations (like test-flow chart) + - name: Discard Prettier changes before Git operations + run: git restore . + + #--------------------------------------------------- + # 3 – Node & dependencies + #--------------------------------------------------- + - name: Set up Node.js + uses: actions/setup-node@v4 + with: { node-version: 18 } # Use a stable Node.js version + + - name: Install dependencies + run: npm install + + #--------------------------------------------------- + # 4 – Install Playwright browsers + #--------------------------------------------------- + - name: Install Playwright and browsers + run: npx playwright install --with-deps + + #--------------------------------------------------- + # 5 – Run Playwright tests + #--------------------------------------------------- + - name: Run Playwright tests + run: | + # Ensure the Playwright output directory exists + # Playwright will put results/traces/videos in `published-screenshots/test-results` as per config. + # The HTML report generation (if not using Playwright's built-in 'html' reporter directly) + # will also use this base directory. + mkdir -p published-screenshots/html || true # Ensure html subfolder for reporter output + echo "Running Playwright tests, outputting artifacts to: published-screenshots" + # Run Playwright tests. `npx playwright test` by default runs in headless mode in CI. + # The `|| true` ensures the workflow continues even if tests fail, so reports can be generated. + npx playwright test --reporter=html,blob --output=published-screenshots || true + + echo "Contents of published-screenshots after tests:" # For debugging + ls -laR published-screenshots || true + + # --- START OF GITHUB PAGES DEPLOYMENT STEPS FOR PLAYWRIGHT REPORTS --- + + # 1. Prepare Playwright Reports and Images for GitHub Pages + - name: Prepare Playwright Reports for GitHub Pages + if: github.event_name == 'pull_request' # Only prepare and deploy for Pull Requests + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + # Define the target directory structure within the `gh-pages-build` folder + # This will correspond to `https:///pr//` + GH_PAGES_TARGET_BASE_DIR="gh-pages-build/pr/$PR_NUMBER" + + # Create directories for the HTML report and individual images/diffs + mkdir -p "$GH_PAGES_TARGET_BASE_DIR/html-report" + mkdir -p "$GH_PAGES_TARGET_BASE_DIR/images" + + echo "Preparing content for GitHub Pages under: $GH_PAGES_TARGET_BASE_DIR" + + # Copy the entire HTML report generated by Playwright + if [ -d "playwright-report" ]; then # Playwright's default HTML report dir + echo "Found Playwright HTML report at 'playwright-report'." + cp -r playwright-report/* "$GH_PAGES_TARGET_BASE_DIR/html-report/" || true + elif [ -d "published-screenshots/html" ]; then # If using custom output path from config + echo "Found Playwright HTML report at 'published-screenshots/html'." + cp -r published-screenshots/html/* "$GH_PAGES_TARGET_BASE_DIR/html-report/" || true + else + echo "Warning: Playwright HTML report directory not found in expected locations." + fi + + # Copy all Playwright-generated PNGs (actual, expected, diff, and any explicit screenshots) + # These are usually found in `published-screenshots/snapshots` or `published-screenshots/test-results` + # and also in the root of `published-screenshots` if --output was used broadly. + find published-screenshots -name "*.png" -exec cp -f {} "$GH_PAGES_TARGET_BASE_DIR/images/" \; || true + echo "Copied all PNGs to $GH_PAGES_TARGET_BASE_DIR/images/" + + # Debugging: show the structure that will be deployed + echo "Contents of 'gh-pages-build' directory:" + ls -laR gh-pages-build || true + + # 2. Deploy to GitHub Pages + - name: Deploy Playwright Reports to GitHub Pages + if: github.event_name == 'pull_request' # Only deploy for PRs + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./gh-pages-build # The local directory containing the content to publish + publish_branch: gh-pages # The branch to publish to + # cname: 'your-custom-domain.com' # Uncomment and replace if you use a custom domain + # force_orphan: true # Use with caution: this can delete previous reports if not carefully managed. + # It's better to manage unique paths per PR number as we do. + + # 3. Generate Markdown for PR Comment (with GitHub Pages URLs) + - name: Generate Playwright Reports Markdown for PR Comment + id: generate_playwright_markdown + if: github.event_name == 'pull_request' # Only for PRs + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + # IMPORTANT: Adjust 'digitalproductinnovationanddevelopment/Code-Reviews-of-GUI-Tests' + # to match your actual GitHub username/organization and repository name. + GITHUB_PAGES_BASE_URL="https://digitalproductinnovationanddevelopment.github.io/Code-Reviews-of-GUI-Tests" + + REPORT_URL="${GITHUB_PAGES_BASE_URL}/pr/${PR_NUMBER}/html-report/" + IMAGES_URL="${GITHUB_PAGES_BASE_URL}/pr/${PR_NUMBER}/images/" + + COMMENT_MARKDOWN="## 📊 Playwright Reports\n\n" + COMMENT_MARKDOWN+="* **Full HTML Report:** [View Report](${REPORT_URL})\n" + COMMENT_MARKDOWN+="* **Captured Images/Diffs:** [Browse Images](${IMAGES_URL})\n\n" + COMMENT_MARKDOWN+="These reports are specific to this Pull Request and are hosted on GitHub Pages.\n" + + echo "playwright_reports_markdown<> $GITHUB_OUTPUT + echo "$COMMENT_MARKDOWN" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + shell: bash + + # --- END OF GITHUB PAGES DEPLOYMENT STEPS --- + + #--------------------------------------------------- + # 6 – Upload Playwright Result JSON (as artifact, optional) + #--------------------------------------------------- + - name: Upload Playwright Result JSON + if: always() # Upload even if tests fail + uses: actions/upload-artifact@v4 + with: + name: playwright-results-json + path: published-screenshots/results.json # Path to the blob reporter's output + if-no-files-found: ignore # Don't fail if the file isn't there + + #--------------------------------------------------- + # 7 – Extract test summary from Playwright results.json + #--------------------------------------------------- + - name: Extract test summary + id: summary + run: | + REPORT_FILE="published-screenshots/results.json" + echo "Attempting to extract summary from: $REPORT_FILE" + + if [ ! -f "$REPORT_FILE" ]; then + echo "Error: Playwright report file not found at $REPORT_FILE" + # Set default values if report is missing to prevent later failures + echo "total=0" >> $GITHUB_OUTPUT + echo "passed=0" >> "$GITHUB_OUTPUT" + echo "failed=0" >> "$GITHUB_OUTPUT" + echo "skipped=0" >> "$GITHUB_OUTPUT" + echo "duration=0" >> "$GITHUB_OUTPUT" + echo "passrate=0.00" >> "$GITHUB_OUTPUT" + exit 0 # Allow workflow to continue even if report is missing + fi + + TOTAL=$(jq '.stats.total' "$REPORT_FILE") + PASSED=$(jq '.stats.expected' "$REPORT_FILE") + FAILED=$(jq '.stats.failures' "$REPORT_FILE") + SKIPPED=$(jq '.stats.skipped' "$REPORT_FILE") + DURATION=$(jq '.stats.duration' "$REPORT_FILE") + PASS_RATE=$(awk "BEGIN{printf \"%.2f\", ($TOTAL==0)?0:($PASSED/$TOTAL)*100}") + + echo "total=$TOTAL" >> "$GITHUB_OUTPUT" + echo "passed=$PASSED" >> "$GITHUB_OUTPUT" + echo "failed=$FAILED" >> "$GITHUB_OUTPUT" + echo "skipped=$SKIPPED" >> "$GITHUB_OUTPUT" + echo "duration=$DURATION" >> "$GITHUB_OUTPUT" + echo "passrate=$PASS_RATE" >> "$GITHUB_OUTPUT" + + #--------------------------------------------------- + # 8 – ESLint (tests only) + #--------------------------------------------------- + - name: Run ESLint on GUI tests + shell: bash + run: | + if [ -d "tests" ]; then + echo "Running ESLint on tests directory." + mkdir -p reports/eslint || true # Ensure directory exists for output + # Run ESLint, output to JSON file, allow workflow to continue if errors found + npx eslint "tests/**/*.{js,ts,tsx}" -f json -o reports/eslint/eslint-tests.json || true + ls -lh reports/eslint/eslint-tests.json || true # Debugging + else + echo "tests/ directory not found, skipping ESLint." + mkdir -p reports/eslint || true + echo "[]" > reports/eslint/eslint-tests.json # Create empty file for consistent behavior + fi + + - name: Upload ESLint report + if: always() + uses: actions/upload-artifact@v4 + with: + name: eslint-test-report + path: reports/eslint/eslint-tests.json + if-no-files-found: ignore + + - name: Read ESLint report (preview) + id: lint_summary + run: | + if [ -f "reports/eslint/eslint-tests.json" ]; then + echo 'summary<> "$GITHUB_OUTPUT" + jq '.' reports/eslint/eslint-tests.json | head -n 20 >> "$GITHUB_OUTPUT" # Pretty print first 20 lines + echo 'EOF' >> "$GITHUB_OUTPUT" + else + echo 'summary<> "$GITHUB_OUTPUT" + echo 'ESLint report not generated or found.' >> "$GITHUB_OUTPUT" + echo 'EOF' >> "$GITHUB_OUTPUT" + fi + + #--------------------------------------------------- + # 9 – Extract ESLint summary + #--------------------------------------------------- + - name: Extract ESLint summary + id: eslint_summary + run: | + REPORT="reports/eslint/eslint-tests.json" + if [ ! -f "$REPORT" ]; then + echo "Error: ESLint report file not found at $REPORT. Cannot extract summary." + echo "total_files=0" >> "$GITHUB_OUTPUT" + echo "errors=0" >> "$GITHUB_OUTPUT" + echo "warnings=0" >> "$GITHUB_OUTPUT" + echo "fixable_errors=0" >> "$GITHUB_OUTPUT" + echo "fixable_warnings=0" >> "$GITHUB_OUTPUT" + exit 0 + fi + TOTAL_FILES=$(jq length "$REPORT") + ERRORS=$(jq '[.[] | .errorCount] | add' "$REPORT") + WARNINGS=$(jq '[.[] | .warningCount] | add' "$REPORT") + FIXABLE_ERRORS=$(jq '[.[] | .fixableErrorCount] | add' "$REPORT") + FIXABLE_WARNINGS=$(jq '[.[] | .fixableWarningCount] | add' "$REPORT") + echo "total_files=$TOTAL_FILES" >> "$GITHUB_OUTPUT" + echo "errors=$ERRORS" >> "$GITHUB_OUTPUT" + echo "warnings=$WARNINGS" >> "$GITHUB_OUTPUT" + echo "fixable_warnings=$FIXABLE_WARNINGS" >> "$GITHUB_OUTPUT" + echo "fixable_errors=$FIXABLE_ERRORS" >> "$GITHUB_OUTPUT" + + #--------------------------------------------------- + # 10 – Generate Suite→Spec Mermaid chart (flowchart.png) + #--------------------------------------------------- + - name: Generate test-flow chart + shell: bash + run: | + export LC_ALL=C # Ensure 'tr' behaves consistently + set -e # Exit on first error for script robustness + + REPORT_FILE="published-screenshots/results.json" + echo "Attempting to generate flowchart from: $REPORT_FILE" + + if [ ! -f "$REPORT_FILE" ]; then + echo "Error: Playwright report file not found at $REPORT_FILE for flowchart generation. Skipping chart." + exit 0 # Exit this step gracefully + fi + + echo "graph TD" > flowchart.mmd + + # Use jq to parse the JSON and format for Mermaid, sanitizing IDs + jq -r ' + .suites[] as $file | ($file.title // "NO_FILE_TITLE") as $fileTitle | $file.suites[]? as $suite | ($suite.title // "NO_SUITE_TITLE") as $suiteTitle | $suite.specs[]? as $spec | ($spec.title // "NO_SPEC_TITLE") as $specTitle | [$fileTitle, $suiteTitle, $specTitle] | @tsv - ' "$REPORT" | - while IFS=$'\t' read -r fileTitle suiteTitle specTitle; do - # Build unique, safe IDs by combining parent and child - fileId=$(echo "$fileTitle" | tr -c 'A-Za-z0-9' '_' | sed 's/^_*\|_*$//g') - suiteId=$(echo "${fileTitle}_${suiteTitle}" | tr -c 'A-Za-z0-9' '_' | sed 's/^_*\|_*$//g') - specId=$(echo "${fileTitle}_${suiteTitle}_${specTitle}" | tr -c 'A-Za-z0-9' '_' | sed 's/^_*\|_*$//g') - - # File node - if ! grep -q "^ ${fileId}\[" flowchart.mmd; then - echo " ${fileId}[\"${fileTitle}\"]" >> flowchart.mmd - fi - # Suite node - if ! grep -q "^ ${suiteId}\[" flowchart.mmd; then - echo " ${suiteId}[\"${suiteTitle}\"]" >> flowchart.mmd - echo " ${fileId} --> ${suiteId}" >> flowchart.mmd - fi - # Spec node/edge - echo " ${suiteId} --> ${specId}[\"${specTitle}\"]" >> flowchart.mmd - done - - printf '{ "args": ["--no-sandbox","--disable-setuid-sandbox"] }\n' > puppeteer.json - - npx -y @mermaid-js/mermaid-cli@10.6.1 \ - -p puppeteer.json \ - -i flowchart.mmd \ - -o flowchart.png - - ls -lh flowchart.png - - - - name: Show flowchart.mmd for debugging - run: cat flowchart.mmd - - - name: Upload test-flow chart - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-flow-chart - path: flowchart.png - - #--------------------------------------------------- - # 11 – Sticky PR comment - #--------------------------------------------------- - - name: Comment on PR with results - uses: marocchino/sticky-pull-request-comment@v2 - with: - message: | - ## Playwright Test Metrics - *Total:* **${{ steps.summary.outputs.total }}** - *Passed:* **${{ steps.summary.outputs.passed }}** - *Failed:* **${{ steps.summary.outputs.failed }}** - *Skipped:* **${{ steps.summary.outputs.skipped }}** - - *Duration:* **${{ steps.summary.outputs.duration }} ms** - *Pass Rate:* **${{ steps.summary.outputs.passrate }} %** - - - ## ESLint Test Metrics - *Files Checked:* **${{ steps.eslint_summary.outputs.total_files }}** - *Errors:* **${{ steps.eslint_summary.outputs.errors }}** - *Warnings:* **${{ steps.eslint_summary.outputs.warnings }}** - *Fixable Errors:* **${{ steps.eslint_summary.outputs.fixable_errors }}** - *Fixable Warnings:* **${{ steps.eslint_summary.outputs.fixable_warnings }}** - ``` - ${{ steps.lint_summary.outputs.summary }} - ``` - - ## Test-Flow Chart - Artifact: **test-flow-chart → flowchart.png** - - _Full run details:_ [link](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) \ No newline at end of file + ' "$REPORT_FILE" | + while IFS=$'\t' read -r fileTitle suiteTitle specTitle; do + # Sanitize titles to create valid Mermaid IDs (alphanumeric, underscores, limited length) + fileId=$(echo "$fileTitle" | sed 's/[^a-zA-Z0-9_]/_/g' | sed 's/^_*\|_*$//g' | cut -c 1-50) + suiteId=$(echo "${fileTitle}_${suiteTitle}" | sed 's/[^a-zA-Z0-9_]/_/g' | sed 's/^_*\|_*$//g' | cut -c 1-50) + specId=$(echo "${fileTitle}_${suiteTitle}_${specTitle}" | sed 's/[^a-zA-Z0-9_]/_/g' | sed 's/^_*\|_*$//g' | cut -c 1-50) + + if [ -z "$fileId" ] || [ -z "$suiteId" ] || [ -z "$specId" ]; then + echo "Warning: Skipped a test entry due to invalid/empty IDs after sanitization. Original: File='$fileTitle', Suite='$suiteTitle', Spec='$specTitle'" + continue + fi + + # Add nodes and edges, checking for duplicates to avoid Mermaid syntax errors + if ! grep -q "^ ${fileId}\\[" flowchart.mmd; then + echo " ${fileId}[\"${fileTitle}\"]" >> flowchart.mmd + fi + if ! grep -q "^ ${suiteId}\\[" flowchart.mmd; then + echo " ${suiteId}[\"${suiteTitle}\"]" >> flowchart.mmd + echo " ${fileId} --> ${suiteId}" >> flowchart.mmd + fi + echo " ${suiteId} --> ${specId}[\"${specTitle}\"]" >> flowchart.mmd + done + + # Configuration for Puppeteer (used by mermaid-cli) to run in CI environment + printf '{ "args": ["--no-sandbox","--disable-setuid-sandbox"] }\n' > puppeteer.json + + # Run mermaid-cli to generate the flowchart image + npx -y @mermaid-js/mermaid-cli@10.6.1 \ + -p puppeteer.json \ + -i flowchart.mmd \ + -o flowchart.png || true # Add || true to allow subsequent steps if mermaid-cli fails + + ls -lh flowchart.png || true # List generated file for debugging + + - name: Show flowchart.mmd for debugging + run: cat flowchart.mmd || true + + - name: Upload test-flow chart + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-flow-chart + path: flowchart.png + if-no-files-found: ignore + + #--------------------------------------------------- + # 11 – Sticky PR comment + #--------------------------------------------------- + - name: Comment on PR with results + uses: marocchino/sticky-pull-request-comment@v2 + with: + message: | + ## Playwright Test Metrics + *Total:* **${{ steps.summary.outputs.total }}** + *Passed:* **${{ steps.summary.outputs.passed }}** + *Failed:* **${{ steps.summary.outputs.failed }}** + *Skipped:* **${{ steps.summary.outputs.skipped }}** + + *Duration:* **${{ steps.summary.outputs.duration }} ms** + *Pass Rate:* **${{ steps.summary.outputs.passrate }} %** + + ${{ steps.generate_playwright_markdown.outputs.playwright_reports_markdown }} # Use the generated markdown for GH Pages links + + ## ESLint (GUI tests) + *Total files scanned:* **${{ steps.eslint_summary.outputs.total_files }}** + *Errors:* **${{ steps.eslint_summary.outputs.errors }}** + *Warnings:* **${{ steps.eslint_summary.outputs.warnings }}** + *Fixable Errors:* **${{ steps.eslint_summary.outputs.fixable_errors }}** + *Fixable Warnings:* **${{ steps.eslint_summary.outputs.fixable_warnings }}** + ``` + ${{ steps.lint_summary.outputs.summary }} + ``` + + ## Test-Flow Chart + Artifact: **test-flow-chart → flowchart.png** + + --- + + **View Full Reports (Downloadable Artifacts):** + You can download the following artifacts from this workflow run's summary page: + * **`playwright-results-json`**: The raw Playwright results in JSON format. + * **`eslint-test-report`**: Detailed ESLint results in JSON format. + + _Full run details:_ [link](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) diff --git a/playwright.config.js b/playwright.config.js index fd6b1b23..9942d583 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,16 +1,62 @@ -const { defineConfig } = require('@playwright/test'); +import { defineConfig, devices } from '@playwright/test'; -module.exports = defineConfig({ +export default defineConfig({ + // Directory where tests are located testDir: './tests', + + // Directory for test artifacts (videos, traces, screenshots generated during runs) + outputDir: 'published-screenshots/test-results', + + // Directory for visual snapshot baseline images. + // When you run `npx playwright test --update-snapshots`, images are saved here. + // When you run `npx playwright test`, images are compared against baselines here. + snapshotPathTemplate: 'published-screenshots/snapshots/{testFilePath}-{arg}-{projectName}{ext}', + + // Run tests in parallel + fullyParallel: true, + + // Fail the build on CI if you accidentally left test.only in the source code. + forbidOnly: !!process.env.CI, + + // Retry on CI only + retries: process.env.CI ? 2 : 0, + + // Opt out of parallel tests on CI. + workers: process.env.CI ? 1 : undefined, + + // Reporter to use. 'blob' is good for CI as it produces a single JSON file. + // The interactive HTML report will be generated in the workflow. + reporter: 'blob', + use: { - headless: true, - screenshot: 'on', - trace: 'on', - video: 'off', - ignoreHTTPSErrors: true, + // Base URL to use in actions like `await page.goto('/')`. + // baseURL: 'http://127.0.0.1:3000', // Uncomment and set if you have a local dev server + + // Collect trace when retrying the first time. + trace: 'on-first-retry', }, - reporter: [ - ['json', { outputFile: 'playwright-metrics.json' }], - ['html', { outputFile: 'report.html', open: 'never' }], + + // Configure projects for different browsers + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + // You can uncomment and add more projects for different browsers if needed: + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, ], -}); \ No newline at end of file + + // If your tests require a development server to be running: + // webServer: { + // command: 'npm run start', // Replace with your actual start command (e.g., 'npm start', 'yarn dev') + // url: 'http://127.0.0.1:3000', // Replace with the URL your app runs on locally + // reuseExistingServer: !process.env.CI, // Reuse server if already running locally + // }, +}); diff --git a/published-screenshots/.last-run.json b/published-screenshots/.last-run.json new file mode 100644 index 00000000..d8c6d97e --- /dev/null +++ b/published-screenshots/.last-run.json @@ -0,0 +1,6 @@ +{ + "status": "failed", + "failedTests": [ + "849da1cee0c412ce5534-4ebb083191c59dda5f31" + ] +} \ No newline at end of file diff --git a/published-screenshots/demo-todo-app-Clear-comple-576e5-no-items-that-are-completed-chromium/error-context.md b/published-screenshots/demo-todo-app-Clear-comple-576e5-no-items-that-are-completed-chromium/error-context.md new file mode 100644 index 00000000..c3136a2e --- /dev/null +++ b/published-screenshots/demo-todo-app-Clear-comple-576e5-no-items-that-are-completed-chromium/error-context.md @@ -0,0 +1,40 @@ +# Page snapshot + +```yaml +- text: This is just a demo of TodoMVC for testing, not the +- link "real TodoMVC app.": + - /url: https://todomvc.com/ +- heading "todos" [level=1] +- textbox "What needs to be done?" +- checkbox "❯Mark all as complete" +- text: ❯Mark all as complete +- list: + - listitem: + - checkbox "Toggle Todo" + - text: feed the cat + - listitem: + - checkbox "Toggle Todo" + - text: book a doctors appointment +- strong: "2" +- text: items left +- list: + - listitem: + - link "All": + - /url: "#/" + - listitem: + - link "Active": + - /url: "#/active" + - listitem: + - link "Completed": + - /url: "#/completed" +- contentinfo: + - paragraph: Double-click to edit a todo + - paragraph: + - text: Created by + - link "Remo H. Jansen": + - /url: http://github.com/remojansen/ + - paragraph: + - text: Part of + - link "TodoMVC": + - /url: http://todomvc.com +``` \ No newline at end of file diff --git a/published-screenshots/demo-todo-app-Clear-comple-576e5-no-items-that-are-completed-chromium/test-failed-1.png b/published-screenshots/demo-todo-app-Clear-comple-576e5-no-items-that-are-completed-chromium/test-failed-1.png new file mode 100644 index 00000000..fa147bca Binary files /dev/null and b/published-screenshots/demo-todo-app-Clear-comple-576e5-no-items-that-are-completed-chromium/test-failed-1.png differ diff --git a/published-screenshots/demo-todo-app-Clear-comple-9aaec-ompleted-items-when-clicked-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Clear-comple-9aaec-ompleted-items-when-clicked-chromium/test-finished-1.png new file mode 100644 index 00000000..24c33816 Binary files /dev/null and b/published-screenshots/demo-todo-app-Clear-comple-9aaec-ompleted-items-when-clicked-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Clear-comple-9aaec-ompleted-items-when-clicked-chromium/todo-app-after-clearing-completed-actual.png b/published-screenshots/demo-todo-app-Clear-comple-9aaec-ompleted-items-when-clicked-chromium/todo-app-after-clearing-completed-actual.png new file mode 100644 index 00000000..24c33816 Binary files /dev/null and b/published-screenshots/demo-todo-app-Clear-comple-9aaec-ompleted-items-when-clicked-chromium/todo-app-after-clearing-completed-actual.png differ diff --git a/published-screenshots/demo-todo-app-Clear-comple-fd1bf-ld-display-the-correct-text-chromium/clear-completed-button-visible-actual.png b/published-screenshots/demo-todo-app-Clear-comple-fd1bf-ld-display-the-correct-text-chromium/clear-completed-button-visible-actual.png new file mode 100644 index 00000000..56c0c833 Binary files /dev/null and b/published-screenshots/demo-todo-app-Clear-comple-fd1bf-ld-display-the-correct-text-chromium/clear-completed-button-visible-actual.png differ diff --git a/published-screenshots/demo-todo-app-Clear-comple-fd1bf-ld-display-the-correct-text-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Clear-comple-fd1bf-ld-display-the-correct-text-chromium/test-finished-1.png new file mode 100644 index 00000000..0b0d46eb Binary files /dev/null and b/published-screenshots/demo-todo-app-Clear-comple-fd1bf-ld-display-the-correct-text-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/test-finished-1.png new file mode 100644 index 00000000..e8bcaf36 Binary files /dev/null and b/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/todo-counter-1-item-actual.png b/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/todo-counter-1-item-actual.png new file mode 100644 index 00000000..c77fa20b Binary files /dev/null and b/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/todo-counter-1-item-actual.png differ diff --git a/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/todo-counter-2-items-actual.png b/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/todo-counter-2-items-actual.png new file mode 100644 index 00000000..7eecf850 Binary files /dev/null and b/published-screenshots/demo-todo-app-Counter-shou-c1b8c-urrent-number-of-todo-items-chromium/todo-counter-2-items-actual.png differ diff --git a/published-screenshots/demo-todo-app-Editing-shou-38188-pty-text-string-was-entered-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Editing-shou-38188-pty-text-string-was-entered-chromium/test-finished-1.png new file mode 100644 index 00000000..89169198 Binary files /dev/null and b/published-screenshots/demo-todo-app-Editing-shou-38188-pty-text-string-was-entered-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Editing-shou-69f34-other-controls-when-editing-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Editing-shou-69f34-other-controls-when-editing-chromium/test-finished-1.png new file mode 100644 index 00000000..ca492361 Binary files /dev/null and b/published-screenshots/demo-todo-app-Editing-shou-69f34-other-controls-when-editing-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Editing-shou-69f34-other-controls-when-editing-chromium/todo-item-editing-mode-actual.png b/published-screenshots/demo-todo-app-Editing-shou-69f34-other-controls-when-editing-chromium/todo-item-editing-mode-actual.png new file mode 100644 index 00000000..a0bfbad6 Binary files /dev/null and b/published-screenshots/demo-todo-app-Editing-shou-69f34-other-controls-when-editing-chromium/todo-item-editing-mode-actual.png differ diff --git a/published-screenshots/demo-todo-app-Editing-should-cancel-edits-on-escape-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Editing-should-cancel-edits-on-escape-chromium/test-finished-1.png new file mode 100644 index 00000000..3a4b062e Binary files /dev/null and b/published-screenshots/demo-todo-app-Editing-should-cancel-edits-on-escape-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Editing-should-save-edits-on-blur-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Editing-should-save-edits-on-blur-chromium/test-finished-1.png new file mode 100644 index 00000000..ae749603 Binary files /dev/null and b/published-screenshots/demo-todo-app-Editing-should-save-edits-on-blur-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Editing-should-trim-entered-text-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Editing-should-trim-entered-text-chromium/test-finished-1.png new file mode 100644 index 00000000..ae749603 Binary files /dev/null and b/published-screenshots/demo-todo-app-Editing-should-trim-entered-text-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Item-should--41727-o-un-mark-items-as-complete-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Item-should--41727-o-un-mark-items-as-complete-chromium/test-finished-1.png new file mode 100644 index 00000000..de051128 Binary files /dev/null and b/published-screenshots/demo-todo-app-Item-should--41727-o-un-mark-items-as-complete-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/test-finished-1.png new file mode 100644 index 00000000..ae749603 Binary files /dev/null and b/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/todo-app-after-edit-actual.png b/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/todo-app-after-edit-actual.png new file mode 100644 index 00000000..ae749603 Binary files /dev/null and b/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/todo-app-after-edit-actual.png differ diff --git a/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/todo-app-before-edit-actual.png b/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/todo-app-before-edit-actual.png new file mode 100644 index 00000000..83058e83 Binary files /dev/null and b/published-screenshots/demo-todo-app-Item-should-allow-me-to-edit-an-item-chromium/todo-app-before-edit-actual.png differ diff --git a/published-screenshots/demo-todo-app-Item-should-allow-me-to-mark-items-as-complete-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Item-should-allow-me-to-mark-items-as-complete-chromium/test-finished-1.png new file mode 100644 index 00000000..294a8c41 Binary files /dev/null and b/published-screenshots/demo-todo-app-Item-should-allow-me-to-mark-items-as-complete-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Item-should-allow-me-to-mark-items-as-complete-chromium/todo-app-first-item-completed-actual.png b/published-screenshots/demo-todo-app-Item-should-allow-me-to-mark-items-as-complete-chromium/todo-app-first-item-completed-actual.png new file mode 100644 index 00000000..6cc0781c Binary files /dev/null and b/published-screenshots/demo-todo-app-Item-should-allow-me-to-mark-items-as-complete-chromium/todo-app-first-item-completed-actual.png differ diff --git a/published-screenshots/demo-todo-app-Mark-all-as--1144c-mark-all-items-as-completed-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Mark-all-as--1144c-mark-all-items-as-completed-chromium/test-finished-1.png new file mode 100644 index 00000000..169afe29 Binary files /dev/null and b/published-screenshots/demo-todo-app-Mark-all-as--1144c-mark-all-items-as-completed-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Mark-all-as--1144c-mark-all-items-as-completed-chromium/todo-app-all-completed-actual.png b/published-screenshots/demo-todo-app-Mark-all-as--1144c-mark-all-items-as-completed-chromium/todo-app-all-completed-actual.png new file mode 100644 index 00000000..169afe29 Binary files /dev/null and b/published-screenshots/demo-todo-app-Mark-all-as--1144c-mark-all-items-as-completed-chromium/todo-app-all-completed-actual.png differ diff --git a/published-screenshots/demo-todo-app-Mark-all-as--1aefc-complete-state-of-all-items-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Mark-all-as--1aefc-complete-state-of-all-items-chromium/test-finished-1.png new file mode 100644 index 00000000..83058e83 Binary files /dev/null and b/published-screenshots/demo-todo-app-Mark-all-as--1aefc-complete-state-of-all-items-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Mark-all-as--1aefc-complete-state-of-all-items-chromium/todo-app-all-uncompleted-actual.png b/published-screenshots/demo-todo-app-Mark-all-as--1aefc-complete-state-of-all-items-chromium/todo-app-all-uncompleted-actual.png new file mode 100644 index 00000000..83058e83 Binary files /dev/null and b/published-screenshots/demo-todo-app-Mark-all-as--1aefc-complete-state-of-all-items-chromium/todo-app-all-uncompleted-actual.png differ diff --git a/published-screenshots/demo-todo-app-Mark-all-as--f0803-items-are-completed-cleared-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Mark-all-as--f0803-items-are-completed-cleared-chromium/test-finished-1.png new file mode 100644 index 00000000..11efc05b Binary files /dev/null and b/published-screenshots/demo-todo-app-Mark-all-as--f0803-items-are-completed-cleared-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Mark-all-as--f0803-items-are-completed-cleared-chromium/todo-app-one-unchecked-actual.png b/published-screenshots/demo-todo-app-Mark-all-as--f0803-items-are-completed-cleared-chromium/todo-app-one-unchecked-actual.png new file mode 100644 index 00000000..83022078 Binary files /dev/null and b/published-screenshots/demo-todo-app-Mark-all-as--f0803-items-are-completed-cleared-chromium/todo-app-one-unchecked-actual.png differ diff --git a/published-screenshots/demo-todo-app-New-Todo-sho-149e5-s-to-the-bottom-of-the-list-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-New-Todo-sho-149e5-s-to-the-bottom-of-the-list-chromium/test-finished-1.png new file mode 100644 index 00000000..8df6f889 Binary files /dev/null and b/published-screenshots/demo-todo-app-New-Todo-sho-149e5-s-to-the-bottom-of-the-list-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-New-Todo-sho-149e5-s-to-the-bottom-of-the-list-chromium/todo-app-default-items-actual.png b/published-screenshots/demo-todo-app-New-Todo-sho-149e5-s-to-the-bottom-of-the-list-chromium/todo-app-default-items-actual.png new file mode 100644 index 00000000..83058e83 Binary files /dev/null and b/published-screenshots/demo-todo-app-New-Todo-sho-149e5-s-to-the-bottom-of-the-list-chromium/todo-app-default-items-actual.png differ diff --git a/published-screenshots/demo-todo-app-New-Todo-sho-28c2c-field-when-an-item-is-added-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-New-Todo-sho-28c2c-field-when-an-item-is-added-chromium/test-finished-1.png new file mode 100644 index 00000000..f3137b29 Binary files /dev/null and b/published-screenshots/demo-todo-app-New-Todo-sho-28c2c-field-when-an-item-is-added-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/test-finished-1.png new file mode 100644 index 00000000..e8bcaf36 Binary files /dev/null and b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-empty-actual.png b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-empty-actual.png new file mode 100644 index 00000000..ac85d212 Binary files /dev/null and b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-empty-actual.png differ diff --git a/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-one-item-actual.png b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-one-item-actual.png new file mode 100644 index 00000000..fab0d34d Binary files /dev/null and b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-one-item-actual.png differ diff --git a/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-two-items-actual.png b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-two-items-actual.png new file mode 100644 index 00000000..8314a87e Binary files /dev/null and b/published-screenshots/demo-todo-app-New-Todo-should-allow-me-to-add-todo-items-chromium/todo-app-two-items-actual.png differ diff --git a/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/test-finished-1.png new file mode 100644 index 00000000..1f15ff5d Binary files /dev/null and b/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/todo-app-after-reload-actual.png b/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/todo-app-after-reload-actual.png new file mode 100644 index 00000000..6cc0781c Binary files /dev/null and b/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/todo-app-after-reload-actual.png differ diff --git a/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/todo-app-before-reload-actual.png b/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/todo-app-before-reload-actual.png new file mode 100644 index 00000000..6cc0781c Binary files /dev/null and b/published-screenshots/demo-todo-app-Persistence-should-persist-its-data-chromium/todo-app-before-reload-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-0becb--to-display-completed-items-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Routing-shou-0becb--to-display-completed-items-chromium/test-finished-1.png new file mode 100644 index 00000000..e493aca6 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-0becb--to-display-completed-items-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-0becb--to-display-completed-items-chromium/todo-app-completed-items-actual.png b/published-screenshots/demo-todo-app-Routing-shou-0becb--to-display-completed-items-chromium/todo-app-completed-items-actual.png new file mode 100644 index 00000000..e493aca6 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-0becb--to-display-completed-items-chromium/todo-app-completed-items-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/test-finished-1.png new file mode 100644 index 00000000..25f9cf66 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-active-selected-actual.png b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-active-selected-actual.png new file mode 100644 index 00000000..9edb4089 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-active-selected-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-all-selected-actual.png b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-all-selected-actual.png new file mode 100644 index 00000000..83058e83 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-all-selected-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-completed-selected-actual.png b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-completed-selected-actual.png new file mode 100644 index 00000000..25f9cf66 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-5c524-he-currently-applied-filter-chromium/todo-filter-completed-selected-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-9be19--me-to-display-active-items-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Routing-shou-9be19--me-to-display-active-items-chromium/test-finished-1.png new file mode 100644 index 00000000..3a57215f Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-9be19--me-to-display-active-items-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Routing-shou-9be19--me-to-display-active-items-chromium/todo-app-active-items-actual.png b/published-screenshots/demo-todo-app-Routing-shou-9be19--me-to-display-active-items-chromium/todo-app-active-items-actual.png new file mode 100644 index 00000000..3a57215f Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-shou-9be19--me-to-display-active-items-chromium/todo-app-active-items-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-should-allow-me-to-display-all-items-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Routing-should-allow-me-to-display-all-items-chromium/test-finished-1.png new file mode 100644 index 00000000..27f2dde1 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-should-allow-me-to-display-all-items-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Routing-should-allow-me-to-display-all-items-chromium/todo-app-all-items-filtered-actual.png b/published-screenshots/demo-todo-app-Routing-should-allow-me-to-display-all-items-chromium/todo-app-all-items-filtered-actual.png new file mode 100644 index 00000000..27f2dde1 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-should-allow-me-to-display-all-items-chromium/todo-app-all-items-filtered-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/test-finished-1.png b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/test-finished-1.png new file mode 100644 index 00000000..ce6d3c35 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/test-finished-1.png differ diff --git a/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-active-items-route-actual.png b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-active-items-route-actual.png new file mode 100644 index 00000000..3a57215f Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-active-items-route-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-all-items-route-actual.png b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-all-items-route-actual.png new file mode 100644 index 00000000..9c67af0a Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-all-items-route-actual.png differ diff --git a/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-completed-items-route-actual.png b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-completed-items-route-actual.png new file mode 100644 index 00000000..e493aca6 Binary files /dev/null and b/published-screenshots/demo-todo-app-Routing-should-respect-the-back-button-chromium/todo-app-completed-items-route-actual.png differ diff --git a/tests/demo-todo-app.spec.ts b/tests/demo-todo-app.spec.ts index 8641cb5f..756f7310 100644 --- a/tests/demo-todo-app.spec.ts +++ b/tests/demo-todo-app.spec.ts @@ -1,437 +1,79 @@ -import { test, expect, type Page } from '@playwright/test'; +import { test, expect } from '@playwright/test'; -test.beforeEach(async ({ page }) => { - await page.goto('https://demo.playwright.dev/todomvc'); -}); - -const TODO_ITEMS = [ - 'buy some cheese', - 'feed the cat', - 'book a doctors appointment' -] as const; - -test.describe('New Todo', () => { - test('should allow me to add todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create 1st todo. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Make sure the list only has one todo item. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0] - ]); - - // Create 2nd todo. - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - - // Make sure the list now has two todo items. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[1] - ]); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); - - test('should clear text input field when an item is added', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create one todo item. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Check that input is empty. - await expect(newTodo).toBeEmpty(); - await checkNumberOfTodosInLocalStorage(page, 1); - }); +test.describe('Todo App Visual Tests', () => { - test('should append new items to the bottom of the list', async ({ page }) => { - // Create 3 items. - await createDefaultTodos(page); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - // Check test using different methods. - await expect(page.getByText('3 items left')).toBeVisible(); - await expect(todoCount).toHaveText('3 items left'); - await expect(todoCount).toContainText('3'); - await expect(todoCount).toHaveText(/3/); - - // Check all items in one call. - await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); - await checkNumberOfTodosInLocalStorage(page, 3); - }); -}); - -test.describe('Mark all as completed', () => { test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); + // Navigate to the ToDo app URL before each test + await page.goto('https://demo.playwright.dev/todomvc'); }); - test.afterEach(async ({ page }) => { - await checkNumberOfTodosInLocalStorage(page, 3); + test('should display the initial empty state correctly', async ({ page }) => { + // Assert that the page title is correct + await expect(page).toHaveTitle(/TodoMVC/); + // ⭐ Visual Regression: Check the entire page's initial empty state + await expect(page).toHaveScreenshot('initial-empty-state.png'); }); - test('should allow me to mark all items as completed', async ({ page }) => { - // Complete all todos. - await page.getByLabel('Mark all as complete').check(); + test('should allow adding new todo items and update count', async ({ page }) => { + // Add first item + await page.locator('.new-todo').fill('Buy milk'); + await page.locator('.new-todo').press('Enter'); + await expect(page.locator('.todo-count')).toHaveText('1 item left'); + // ⭐ Visual Regression: Check state after adding one item + await expect(page).toHaveScreenshot('after-one-item.png'); - // Ensure all todos have 'completed' class. - await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); + // Add second item + await page.locator('.new-todo').fill('Walk the dog'); + await page.locator('.new-todo').press('Enter'); + await expect(page.locator('.todo-count')).toHaveText('2 items left'); + // ⭐ Visual Regression: Check state after adding two items + await expect(page).toHaveScreenshot('after-two-items.png'); }); - test('should allow me to clear the complete state of all items', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - // Check and then immediately uncheck. - await toggleAll.check(); - await toggleAll.uncheck(); + test('should mark an item as completed', async ({ page }) => { + // Add an item + await page.locator('.new-todo').fill('Learn Playwright'); + await page.locator('.new-todo').press('Enter'); + await expect(page.locator('.todo-count')).toHaveText('1 item left'); - // Should be no completed classes. - await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + // Mark as completed + await page.locator('.toggle').check(); + await expect(page.locator('.todo-count')).toHaveText('0 items left'); + // ⭐ Visual Regression: Check state after completing an item + await expect(page).toHaveScreenshot('item-completed.png'); }); - test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - await toggleAll.check(); - await expect(toggleAll).toBeChecked(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Uncheck first todo. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').uncheck(); - - // Reuse toggleAll locator and make sure its not checked. - await expect(toggleAll).not.toBeChecked(); + test('should filter completed items', async ({ page }) => { + // Add multiple items + await page.locator('.new-todo').fill('Item 1'); + await page.locator('.new-todo').press('Enter'); + await page.locator('.new-todo').fill('Item 2'); + await page.locator('.new-todo').press('Enter'); - await firstTodo.getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); + // Mark first item as completed + await page.locator('.todo-list li').filter({ hasText: 'Item 1' }).locator('.toggle').check(); - // Assert the toggle all is checked again. - await expect(toggleAll).toBeChecked(); + // Click on "Completed" filter + await page.locator('.filters >> text=Completed').click(); + // ⭐ Visual Regression: Check state with "Completed" filter active + await expect(page).toHaveScreenshot('filter-completed.png'); }); -}); - -test.describe('Item', () => { - - test('should allow me to mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } + test('should clear completed items', async ({ page }) => { + // Add multiple items + await page.locator('.new-todo').fill('Item 1'); + await page.locator('.new-todo').press('Enter'); + await page.locator('.new-todo').fill('Item 2'); + await page.locator('.new-todo').press('Enter'); - // Check first item. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').check(); - await expect(firstTodo).toHaveClass('completed'); + // Mark both items as completed + await page.locator('.toggle').first().check(); + await page.locator('.toggle').last().check(); - // Check second item. - const secondTodo = page.getByTestId('todo-item').nth(1); - await expect(secondTodo).not.toHaveClass('completed'); - await secondTodo.getByRole('checkbox').check(); - - // Assert completed class. - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).toHaveClass('completed'); + // Clear completed + await page.locator('.clear-completed').click(); + // ⭐ Visual Regression: Check state after clearing completed items + await expect(page).toHaveScreenshot('after-clear-completed.png'); }); - test('should allow me to un-mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const firstTodo = page.getByTestId('todo-item').nth(0); - const secondTodo = page.getByTestId('todo-item').nth(1); - const firstTodoCheckbox = firstTodo.getByRole('checkbox'); - - await firstTodoCheckbox.check(); - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await firstTodoCheckbox.uncheck(); - await expect(firstTodo).not.toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 0); - }); - - test('should allow me to edit an item', async ({ page }) => { - await createDefaultTodos(page); - - const todoItems = page.getByTestId('todo-item'); - const secondTodo = todoItems.nth(1); - await secondTodo.dblclick(); - await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); - await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); - - // Explicitly assert the new text value. - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2] - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); }); - -test.describe('Editing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should hide other controls when editing', async ({ page }) => { - const todoItem = page.getByTestId('todo-item').nth(1); - await todoItem.dblclick(); - await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); - await expect(todoItem.locator('label', { - hasText: TODO_ITEMS[1], - })).not.toBeVisible(); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should save edits on blur', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should trim entered text', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should remove the item if an empty text string was entered', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[2], - ]); - }); - - test('should cancel edits on escape', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); - await expect(todoItems).toHaveText(TODO_ITEMS); - }); -}); - -test.describe('Counter', () => { - test('should display the current number of todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - await expect(todoCount).toContainText('1'); - - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - await expect(todoCount).toContainText('2'); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); -}); - -test.describe('Clear completed button', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - }); - - test('should display the correct text', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); - }); - - test('should remove completed items when clicked', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).getByRole('checkbox').check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(todoItems).toHaveCount(2); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should be hidden when there are no items that are completed', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); - }); -}); - -test.describe('Persistence', () => { - test('should persist its data', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const todoItems = page.getByTestId('todo-item'); - const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); - await firstTodoCheck.check(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - - // Ensure there is 1 completed item. - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - // Now reload. - await page.reload(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - }); -}); - -test.describe('Routing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - // make sure the app had a chance to save updated todos in storage - // before navigating to a new view, otherwise the items can get lost :( - // in some frameworks like Durandal - await checkTodosInLocalStorage(page, TODO_ITEMS[0]); - }); - - test('should allow me to display active items', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await expect(todoItem).toHaveCount(2); - await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should respect the back button', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await test.step('Showing all items', async () => { - await page.getByRole('link', { name: 'All' }).click(); - await expect(todoItem).toHaveCount(3); - }); - - await test.step('Showing active items', async () => { - await page.getByRole('link', { name: 'Active' }).click(); - }); - - await test.step('Showing completed items', async () => { - await page.getByRole('link', { name: 'Completed' }).click(); - }); - - await expect(todoItem).toHaveCount(1); - await page.goBack(); - await expect(todoItem).toHaveCount(2); - await page.goBack(); - await expect(todoItem).toHaveCount(3); - }); - - test('should allow me to display completed items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Completed' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(1); - }); - - test('should allow me to display all items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await page.getByRole('link', { name: 'Completed' }).click(); - await page.getByRole('link', { name: 'All' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(3); - }); - - test('should highlight the currently applied filter', async ({ page }) => { - await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); - - //create locators for active and completed links - const activeLink = page.getByRole('link', { name: 'Active' }); - const completedLink = page.getByRole('link', { name: 'Completed' }); - await activeLink.click(); - - // Page change - active items. - await expect(activeLink).toHaveClass('selected'); - await completedLink.click(); - - // Page change - completed items. - await expect(completedLink).toHaveClass('selected'); - }); -}); - -async function createDefaultTodos(page: Page) { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } -} - -async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).length === e; - }, expected); -} - -async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; - }, expected); -} - -async function checkTodosInLocalStorage(page: Page, title: string) { - return await page.waitForFunction(t => { - return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); - }, title); -}