7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
496 lines
19 KiB
YAML
496 lines
19 KiB
YAML
name: Puppeteer E2E Tests
|
|
|
|
on:
|
|
pull_request:
|
|
types: [labeled]
|
|
workflow_call: # Called by release.yml (e2e-test-gate)
|
|
secrets:
|
|
OPENROUTER_API_KEY:
|
|
required: false
|
|
SERPER_API_KEY:
|
|
required: false
|
|
workflow_dispatch: # Manual trigger runs all tests
|
|
schedule:
|
|
# Run weekly on Sunday at 2 AM UTC
|
|
- cron: '0 2 * * 0'
|
|
|
|
# No concurrency group — intentionally omitted.
|
|
# This workflow triggers on both pull_request and workflow_call (from
|
|
# ci-gate.yml / release-gate.yml). A shared concurrency key would cause
|
|
# direct PR runs and workflow_call runs to cancel each other mid-flight.
|
|
# See #3554 (reverted in #3599) for context.
|
|
|
|
permissions: {} # Minimal top-level for OSSF Scorecard
|
|
|
|
jobs:
|
|
puppeteer-tests:
|
|
name: Puppeteer E2E Tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 120
|
|
# For pull_request events, only run when labeled test:puppeteer or test:e2e.
|
|
# For all other triggers (push via workflow_call from release.yml,
|
|
# workflow_dispatch, schedule), always run.
|
|
# NOTE: workflow_call inherits the caller's github.event_name (e.g. "push"),
|
|
# so checking for 'workflow_call' never matches — use != 'pull_request' instead.
|
|
if: |
|
|
github.event_name != 'pull_request' ||
|
|
github.event.label.name == 'test:puppeteer' ||
|
|
github.event.label.name == 'test:e2e'
|
|
environment: ci
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
issues: write
|
|
env:
|
|
LABEL_NAME: ${{ github.event.label.name || 'manual' }}
|
|
|
|
steps:
|
|
- name: Harden the runner (Audit all outbound calls)
|
|
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Setup PDM
|
|
uses: pdm-project/setup-pdm@973541a5febeafcfdadf8a51211435be6ecfd90f # v4.5
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: '24'
|
|
|
|
- name: Install system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
# Note: libasound2t64 is the Ubuntu 24.04 name for libasound2
|
|
sudo apt-get install -y libsqlcipher-dev xvfb \
|
|
libnss3 libgbm1 libasound2t64 libatk1.0-0 libatk-bridge2.0-0 \
|
|
libcups2 libdrm2 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
|
|
fonts-liberation xdg-utils
|
|
|
|
- name: Install Python dependencies
|
|
run: pdm install
|
|
|
|
- name: Install root frontend dependencies
|
|
run: npm ci
|
|
|
|
- name: Build Vite frontend bundle
|
|
# Generates src/local_deep_research/web/static/dist/, which the
|
|
# Flask app loads via vite_helper. Without this the page renders
|
|
# without bundled CSS, causing tests to run against a partially
|
|
# unstyled UI (the responsive baseline tracked in PR #3979 was
|
|
# captured against a built bundle).
|
|
run: npm run build
|
|
|
|
- name: Install Puppeteer dependencies
|
|
working-directory: tests/puppeteer
|
|
run: |
|
|
npm ci
|
|
npx puppeteer browsers install chrome
|
|
|
|
- name: Setup directories
|
|
run: |
|
|
mkdir -p data/encrypted_databases
|
|
mkdir -p tests/puppeteer/screenshots
|
|
|
|
- name: Initialize database
|
|
env:
|
|
TEST_ENV: true
|
|
LDR_DATA_DIR: ${{ github.workspace }}/data
|
|
LDR_DB_CONFIG_KDF_ITERATIONS: "1000"
|
|
# Without LDR_TEST_MODE the 1000 is clamped to the production
|
|
# minimum (256000), making SQLCipher ~256x slower — see #4430.
|
|
LDR_TEST_MODE: "1"
|
|
run: |
|
|
cd src
|
|
pdm run python ../scripts/ci/init_test_database.py
|
|
|
|
- name: Create test configuration
|
|
run: |
|
|
# Create config directory if needed
|
|
mkdir -p ~/.config/local-deep-research
|
|
|
|
# Create settings with OpenRouter + Gemini 2.5 Flash Lite
|
|
# (Gemini 2.0 Flash is heavily rate-limited on the OpenRouter free
|
|
# pool; 2.5 Flash Lite is GA, cheaper, and far less contended.)
|
|
cat > ~/.config/local-deep-research/settings.toml << 'EOF'
|
|
[llm]
|
|
provider = "openrouter"
|
|
model = "google/gemini-2.5-flash-lite"
|
|
|
|
[search]
|
|
tool = "serper"
|
|
iterations = 1
|
|
questions_per_iteration = 2
|
|
|
|
[general]
|
|
report_type = "quick"
|
|
EOF
|
|
|
|
- name: Start LDR server
|
|
env:
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
SERPER_API_KEY: ${{ secrets.SERPER_API_KEY }}
|
|
# Settings manager env var overrides (format: LDR_<KEY_WITH_UNDERSCORES>)
|
|
# LLM settings - use OpenRouter with Gemini 2.5 Flash Lite
|
|
LDR_LLM_PROVIDER: openrouter
|
|
LDR_LLM_MODEL: google/gemini-2.5-flash-lite
|
|
LDR_LLM_OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
# Search settings
|
|
LDR_SEARCH_TOOL: serper
|
|
LDR_SEARCH_ENGINE_WEB_SERPER_API_KEY: ${{ secrets.SERPER_API_KEY }}
|
|
# Database and server settings
|
|
LDR_DATA_DIR: ${{ github.workspace }}/data
|
|
LDR_DB_CONFIG_KDF_ITERATIONS: "1000"
|
|
# Without LDR_TEST_MODE the 1000 is clamped to the production
|
|
# minimum (256000), making SQLCipher ~256x slower — see #4430.
|
|
LDR_TEST_MODE: "1"
|
|
CI: true
|
|
LDR_DISABLE_RATE_LIMITING: true
|
|
run: |
|
|
# Start server in background
|
|
cd src
|
|
nohup pdm run python -m local_deep_research.web.app > /tmp/ldr_server.log 2>&1 &
|
|
|
|
echo "Waiting for server to start..."
|
|
for i in {1..60}; do
|
|
if curl -fsS --connect-timeout 2 --max-time 5 http://localhost:5000/api/v1/health 2>/dev/null; then
|
|
echo "Server is ready after $i seconds!"
|
|
break
|
|
fi
|
|
if ! pgrep -f 'python -m local_deep_research.web.app' > /dev/null; then
|
|
echo "Server process died!"
|
|
cat /tmp/ldr_server.log
|
|
exit 1
|
|
fi
|
|
echo "Waiting... ($i/60)"
|
|
sleep 1
|
|
done
|
|
|
|
# Final check if server is ready
|
|
if ! curl -fsS --connect-timeout 2 --max-time 5 http://localhost:5000/api/v1/health 2>/dev/null; then
|
|
echo "Server failed to start. Logs:"
|
|
cat /tmp/ldr_server.log
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run Puppeteer tests
|
|
working-directory: tests/puppeteer
|
|
env:
|
|
TEST_URL: http://localhost:5000
|
|
HEADLESS: true
|
|
run: |
|
|
set -o pipefail
|
|
# Run all test suites with xvfb for virtual display
|
|
xvfb-run -a -s "-screen 0 1920x1080x24" npm run test:ci 2>&1 | tee test-output.log
|
|
TEST_RESULT="${PIPESTATUS[0]}"
|
|
|
|
# Save exit code for downstream steps
|
|
echo "TEST_EXIT_CODE=${TEST_RESULT}" >> "$GITHUB_ENV"
|
|
|
|
# Exit with test result so the step reflects pass/fail
|
|
exit "${TEST_RESULT}"
|
|
|
|
- name: Run egress-policy UI test
|
|
# Release gate for the Privacy & Egress UI (scope dropdown, the
|
|
# require-local toggles, the STRICT+meta-picker guard, and the
|
|
# data-scope propagation across pages). This test lives in
|
|
# tests/ui_tests (a standalone Puppeteer script, NOT the mocha suite
|
|
# above) and runs against the SAME booted server. It authenticates as
|
|
# the CI test user (test_admin) created by init_test_database.py and
|
|
# matched in tests/ui_tests/auth_helper.js, and skips screenshots when
|
|
# CI=true. `if: always()` so its result is reported even when the mocha
|
|
# suite above failed; the script exits non-zero on any failed check, so
|
|
# a regression fails the release gate.
|
|
if: always()
|
|
working-directory: tests/ui_tests
|
|
env:
|
|
BASE_URL: http://localhost:5000
|
|
CI: true
|
|
HEADLESS: true
|
|
run: |
|
|
set -o pipefail
|
|
npm ci
|
|
npx puppeteer browsers install chrome
|
|
xvfb-run -a -s "-screen 0 1920x1080x24" \
|
|
node test_egress_policy_ui.js 2>&1 | tee egress-ui-output.log
|
|
|
|
- name: Extract test summary
|
|
if: always()
|
|
working-directory: tests/puppeteer
|
|
run: |
|
|
# Extract pass/fail summary from mocha output
|
|
if [ -f test-output.log ]; then
|
|
# Get the summary line (e.g., "26 passing (3m)")
|
|
PASSING=$(grep -oP '\d+ passing' test-output.log | tail -1 || echo "0 passing")
|
|
FAILING=$(grep -oP '\d+ failing' test-output.log | tail -1 || echo "0 failing")
|
|
|
|
echo "TESTS_PASSING=${PASSING}" >> "$GITHUB_ENV"
|
|
echo "TESTS_FAILING=${FAILING}" >> "$GITHUB_ENV"
|
|
|
|
# Extract key test results (collection creation, subscription, research)
|
|
echo "## Key Test Results" > test-summary.md
|
|
echo "" >> test-summary.md
|
|
|
|
# Check for collection creation
|
|
if grep -q "Collection found in dropdown: true" test-output.log; then
|
|
echo "- ✅ Collection creation: **Success**" >> test-summary.md
|
|
elif grep -q "Collection found in dropdown: false" test-output.log; then
|
|
echo "- ❌ Collection creation: **Failed**" >> test-summary.md
|
|
fi
|
|
|
|
# Check for subscription creation
|
|
if grep -q "Subscription name found: true" test-output.log; then
|
|
echo "- ✅ Subscription creation: **Success**" >> test-summary.md
|
|
elif grep -q "Subscription name found: false" test-output.log; then
|
|
echo "- ❌ Subscription creation: **Failed**" >> test-summary.md
|
|
fi
|
|
|
|
# Check for research completion
|
|
if grep -q "Research completed: true" test-output.log; then
|
|
RESEARCH_TIME=$(grep -oP "Research completed: true \(took \K\d+(?=s\))" test-output.log || echo "?")
|
|
echo "- ✅ Research workflow: **Completed** (${RESEARCH_TIME}s)" >> test-summary.md
|
|
elif grep -q "Research completed: false" test-output.log; then
|
|
echo "- ⚠️ Research workflow: **Timed out**" >> test-summary.md
|
|
fi
|
|
|
|
# Check for settings persistence
|
|
if grep -q "After reload value:" test-output.log; then
|
|
echo "- ✅ Settings persistence: **Working**" >> test-summary.md
|
|
fi
|
|
|
|
# Add screenshot count
|
|
SCREENSHOT_COUNT=$(find screenshots -name "*.png" 2>/dev/null | wc -l || echo "0")
|
|
echo "" >> test-summary.md
|
|
echo "**Screenshots captured:** ${SCREENSHOT_COUNT}" >> test-summary.md
|
|
fi
|
|
|
|
- name: Show test summary in workflow UI
|
|
if: always()
|
|
working-directory: tests/puppeteer
|
|
# zizmor: ignore[template-injection] — env.TEST_EXIT_CODE/TESTS_PASSING/TESTS_FAILING are set by this workflow, not user input
|
|
run: |
|
|
# Output to GITHUB_STEP_SUMMARY so results are visible in Actions UI
|
|
{
|
|
echo "## 🧪 Puppeteer E2E Test Results"
|
|
echo ""
|
|
if [ "$TEST_EXIT_CODE" == "0" ]; then
|
|
echo "### ✅ Tests Passed!"
|
|
else
|
|
echo "### ❌ Tests Failed"
|
|
fi
|
|
echo ""
|
|
echo "**Summary:** $TESTS_PASSING, $TESTS_FAILING"
|
|
echo ""
|
|
|
|
# Include key test results
|
|
if [ -f test-summary.md ]; then
|
|
cat test-summary.md
|
|
echo ""
|
|
fi
|
|
|
|
echo "---"
|
|
echo ""
|
|
echo "<details>"
|
|
echo "<summary>📋 Last 50 lines of test output</summary>"
|
|
echo ""
|
|
echo '```'
|
|
if [ -f test-output.log ]; then
|
|
tail -50 test-output.log
|
|
fi
|
|
echo '```'
|
|
echo "</details>"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload server logs
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: server-logs
|
|
path: /tmp/ldr_server.log
|
|
retention-days: 7
|
|
|
|
- name: Upload test artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: puppeteer-test-results
|
|
path: |
|
|
tests/puppeteer/test-output.log
|
|
tests/puppeteer/test-summary.md
|
|
tests/puppeteer/screenshots/
|
|
retention-days: 7
|
|
|
|
- name: Find PR number
|
|
id: find-pr
|
|
if: always()
|
|
# zizmor: ignore[template-injection] — github.ref_name and github.repository are safe GitHub context values
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
PR_NUM: ${{ github.event.pull_request.number }}
|
|
REF_NAME: ${{ github.ref_name }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
if [ "$EVENT_NAME" == "pull_request" ]; then
|
|
# Labeled PR run — the primary, correct path. Real branches targeting main.
|
|
echo "pr_number=$PR_NUM" >> "$GITHUB_OUTPUT"
|
|
elif [ "$EVENT_NAME" == "workflow_dispatch" ] && [ "$REF_NAME" != "main" ]; then
|
|
# Manual run on a feature branch. Map branch -> its open PR, but only a
|
|
# UNIQUE, SAME-REPO match: cross-repo (fork) PRs and ambiguous matches are
|
|
# skipped so we never post to the wrong PR (e.g. a fork PR'd from `main`).
|
|
PR_NUMBER=$(gh pr list --head "$REF_NAME" --state open --repo "$REPO" \
|
|
--json number,isCrossRepository \
|
|
--jq '[.[] | select(.isCrossRepository == false)]
|
|
| if length == 1 then .[0].number | tostring else "" end')
|
|
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
|
|
else
|
|
# release.yml workflow_call (event=push) and the weekly schedule both run
|
|
# on the default branch. There is never a PR from main, so any
|
|
# `gh pr list --head main` match is a coincidental fork PR — skip
|
|
# entirely (results still go to the Actions step summary). See #3884.
|
|
echo "pr_number=" >> "$GITHUB_OUTPUT"
|
|
echo "event=$EVENT_NAME ref=$REF_NAME has no associated PR — skipping comment"
|
|
fi
|
|
|
|
- name: Build PR comment
|
|
if: always() && steps.find-pr.outputs.pr_number != ''
|
|
# zizmor: ignore[template-injection] — env.TEST_EXIT_CODE/TESTS_PASSING/TESTS_FAILING are set by this workflow, not user input
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
run: |
|
|
{
|
|
# Hidden marker — the "Post or update PR comment" step finds this
|
|
# comment by this marker so re-runs edit it in place (see #3884).
|
|
echo '<!-- puppeteer-e2e-results -->'
|
|
echo "## 🧪 Puppeteer E2E Test Results"
|
|
echo ""
|
|
if [ "$TEST_EXIT_CODE" == "0" ]; then
|
|
echo "### ✅ Tests Passed!"
|
|
else
|
|
echo "### ❌ Tests Failed"
|
|
fi
|
|
echo ""
|
|
echo "**Summary:** $TESTS_PASSING, $TESTS_FAILING"
|
|
echo ""
|
|
|
|
# Include key test results
|
|
if [ -f tests/puppeteer/test-summary.md ]; then
|
|
cat tests/puppeteer/test-summary.md
|
|
echo ""
|
|
fi
|
|
|
|
# Include research output if available
|
|
if [ -f tests/puppeteer/research-output/research-result.txt ]; then
|
|
echo "---"
|
|
echo ""
|
|
echo "### 📝 Research Output"
|
|
echo ""
|
|
if [ -f tests/puppeteer/research-output/research-metadata.json ]; then
|
|
QUERY=$(jq -r '.query // "unknown"' tests/puppeteer/research-output/research-metadata.json)
|
|
echo "**Query:** \`$QUERY\`"
|
|
echo ""
|
|
fi
|
|
echo "<details>"
|
|
echo "<summary>📄 Research Result (click to expand)</summary>"
|
|
echo ""
|
|
echo '```markdown'
|
|
# Get first 5000 chars of research output
|
|
head -c 5000 tests/puppeteer/research-output/research-result.txt
|
|
TOTAL_SIZE=$(wc -c < tests/puppeteer/research-output/research-result.txt)
|
|
if [ "$TOTAL_SIZE" -gt 5000 ]; then
|
|
echo ""
|
|
echo "... [truncated, total $TOTAL_SIZE chars]"
|
|
fi
|
|
echo '```'
|
|
echo "</details>"
|
|
echo ""
|
|
fi
|
|
|
|
echo "---"
|
|
echo ""
|
|
echo "<details>"
|
|
echo "<summary>📋 Full Test Output (click to expand)</summary>"
|
|
echo ""
|
|
echo '```'
|
|
# Get last 100 lines of test output (most relevant)
|
|
if [ -f tests/puppeteer/test-output.log ]; then
|
|
tail -100 tests/puppeteer/test-output.log
|
|
fi
|
|
echo '```'
|
|
echo "</details>"
|
|
echo ""
|
|
echo "---"
|
|
echo ""
|
|
echo "**Configuration:**"
|
|
echo "- Model: \`google/gemini-2.5-flash-lite\` (via OpenRouter)"
|
|
echo "- Search: \`serper\`"
|
|
echo "- Test suite: \`all\`"
|
|
echo ""
|
|
echo "_Triggered by: \`${EVENT_NAME}\` (label: \`${LABEL_NAME}\`)_"
|
|
} > comment.md
|
|
|
|
- name: Post or update PR comment
|
|
if: always() && steps.find-pr.outputs.pr_number != ''
|
|
continue-on-error: true # required release gate must not fail on a GitHub API hiccup
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
env:
|
|
PR_NUMBER: ${{ steps.find-pr.outputs.pr_number }}
|
|
with:
|
|
retries: 3
|
|
script: |
|
|
const fs = require('fs');
|
|
const issue_number = Number(process.env.PR_NUMBER);
|
|
const marker = '<!-- puppeteer-e2e-results -->';
|
|
const body = fs.readFileSync('comment.md', 'utf8');
|
|
|
|
// Paginate — on a long-lived PR the existing comment may be past the
|
|
// first page (default 30), which would otherwise create a new comment
|
|
// every run and reintroduce the spam this fix removes (#3884).
|
|
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number,
|
|
per_page: 100,
|
|
});
|
|
const existing = comments.find(
|
|
(c) => c.user.type === 'Bot' && c.body.includes(marker)
|
|
);
|
|
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: existing.id,
|
|
body,
|
|
});
|
|
} else {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number,
|
|
body,
|
|
});
|
|
}
|
|
|
|
- name: Remove label for re-triggering
|
|
if: always() && github.event_name == 'pull_request'
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR_NUM: ${{ github.event.pull_request.number }}
|
|
run: |
|
|
gh issue edit "$PR_NUM" --remove-label "$LABEL_NAME" 2>/dev/null || true
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
run: |
|
|
# Kill server
|
|
pkill -f 'python -m local_deep_research.web.app' || true
|
|
rm -f comment.md
|