qai-cli 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/mcp-config.json +12 -0
- package/.claude/qa-engineer-prompt.md +194 -0
- package/.eslintrc.json +69 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +79 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +50 -0
- package/.github/ISSUE_TEMPLATE/security.md +43 -0
- package/.github/dependabot.yml +51 -0
- package/.github/pull_request_template.md +11 -0
- package/.github/workflows/lint.yml +35 -0
- package/.github/workflows/playwright-qa.yml +223 -0
- package/.github/workflows/qa-engineer.yml +309 -0
- package/.github/workflows/visual-regression.yml +192 -0
- package/.prettierrc.json +10 -0
- package/README.md +111 -0
- package/action.yml +149 -0
- package/docs/BUGS.md +43 -0
- package/docs/app.js +101 -0
- package/docs/index.html +129 -0
- package/docs/style.css +315 -0
- package/examples/workflow-local.yml +22 -0
- package/examples/workflow-with-vercel.yml +40 -0
- package/package.json +83 -0
- package/qa-report-agent.md +30 -0
- package/qa-report-kudos.md +35 -0
- package/scripts/aria-snapshot.js +328 -0
- package/scripts/page-utils.js +357 -0
- package/scripts/visual-regression.cjs +339 -0
- package/src/analyze.js +365 -0
- package/src/capture.js +133 -0
- package/src/index.js +204 -0
- package/src/providers/anthropic.js +59 -0
- package/src/providers/base.js +164 -0
- package/src/providers/gemini.js +42 -0
- package/src/providers/index.js +132 -0
- package/src/providers/ollama.js +49 -0
- package/src/providers/openai.js +54 -0
- package/src/types.d.ts +148 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
name: Visual Regression Testing
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
url:
|
|
7
|
+
description: 'URL to test'
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
update_baseline:
|
|
11
|
+
description: 'Update baseline screenshots'
|
|
12
|
+
required: false
|
|
13
|
+
type: boolean
|
|
14
|
+
default: false
|
|
15
|
+
baseline_run_id:
|
|
16
|
+
description: 'Run ID to use as baseline (leave empty for latest)'
|
|
17
|
+
required: false
|
|
18
|
+
type: string
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
pull-requests: write
|
|
23
|
+
issues: write
|
|
24
|
+
actions: read
|
|
25
|
+
|
|
26
|
+
env:
|
|
27
|
+
NODE_VERSION: '20'
|
|
28
|
+
SCREENSHOTS_DIR: './screenshots'
|
|
29
|
+
BASELINE_DIR: './screenshots/baseline'
|
|
30
|
+
DIFF_DIR: './screenshots/diff'
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
visual-regression:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
timeout-minutes: 25
|
|
36
|
+
|
|
37
|
+
steps:
|
|
38
|
+
- name: Checkout repository
|
|
39
|
+
uses: actions/checkout@v4
|
|
40
|
+
|
|
41
|
+
- name: Setup Node.js
|
|
42
|
+
uses: actions/setup-node@v4
|
|
43
|
+
with:
|
|
44
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
45
|
+
cache: 'npm'
|
|
46
|
+
|
|
47
|
+
- name: Install Dependencies
|
|
48
|
+
run: npm ci
|
|
49
|
+
|
|
50
|
+
- name: Install Playwright Browsers
|
|
51
|
+
run: npx playwright install chromium --with-deps
|
|
52
|
+
|
|
53
|
+
- name: Download Baseline Screenshots
|
|
54
|
+
if: ${{ !inputs.update_baseline }}
|
|
55
|
+
uses: dawidd6/action-download-artifact@v3
|
|
56
|
+
with:
|
|
57
|
+
name: visual-baseline
|
|
58
|
+
path: ${{ env.BASELINE_DIR }}
|
|
59
|
+
workflow: visual-regression.yml
|
|
60
|
+
run_id: ${{ inputs.baseline_run_id }}
|
|
61
|
+
if_no_artifact_found: warn
|
|
62
|
+
continue-on-error: true
|
|
63
|
+
|
|
64
|
+
- name: Create Directories
|
|
65
|
+
run: |
|
|
66
|
+
mkdir -p ${{ env.SCREENSHOTS_DIR }}
|
|
67
|
+
mkdir -p ${{ env.BASELINE_DIR }}
|
|
68
|
+
mkdir -p ${{ env.DIFF_DIR }}
|
|
69
|
+
|
|
70
|
+
- name: Capture Screenshots
|
|
71
|
+
env:
|
|
72
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
73
|
+
run: |
|
|
74
|
+
echo "Capturing screenshots for visual regression testing..."
|
|
75
|
+
echo "URL: ${{ inputs.url }}"
|
|
76
|
+
|
|
77
|
+
npx @anthropic-ai/claude-code \
|
|
78
|
+
--print \
|
|
79
|
+
--mcp-config .claude/mcp-config.json \
|
|
80
|
+
-- \
|
|
81
|
+
'Navigate to ${{ inputs.url }} and capture screenshots. Save to ./screenshots/: desktop-full.png at 1920x1080, tablet-full.png at 768x1024, mobile-full.png at 375x667, desktop-fold.png, navigation.png, footer.png. Wait for page load before each screenshot.'
|
|
82
|
+
|
|
83
|
+
- name: Run Visual Regression Comparison
|
|
84
|
+
if: ${{ !inputs.update_baseline }}
|
|
85
|
+
id: comparison
|
|
86
|
+
run: |
|
|
87
|
+
echo "Running visual regression comparison..."
|
|
88
|
+
|
|
89
|
+
if [ -d "${{ env.BASELINE_DIR }}" ] && [ "$(ls -A ${{ env.BASELINE_DIR }} 2>/dev/null)" ]; then
|
|
90
|
+
node scripts/visual-regression.cjs compare \
|
|
91
|
+
"${{ env.BASELINE_DIR }}" \
|
|
92
|
+
"${{ env.SCREENSHOTS_DIR }}" \
|
|
93
|
+
"${{ env.DIFF_DIR }}"
|
|
94
|
+
|
|
95
|
+
# Capture exit code
|
|
96
|
+
EXIT_CODE=$?
|
|
97
|
+
|
|
98
|
+
# Read summary for output
|
|
99
|
+
if [ -f "visual-regression-report.md" ]; then
|
|
100
|
+
echo "report_exists=true" >> $GITHUB_OUTPUT
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
exit $EXIT_CODE
|
|
104
|
+
else
|
|
105
|
+
echo "No baseline found - this will become the new baseline"
|
|
106
|
+
echo "no_baseline=true" >> $GITHUB_OUTPUT
|
|
107
|
+
fi
|
|
108
|
+
continue-on-error: true
|
|
109
|
+
|
|
110
|
+
- name: Update Baseline
|
|
111
|
+
if: ${{ inputs.update_baseline || steps.comparison.outputs.no_baseline == 'true' }}
|
|
112
|
+
run: |
|
|
113
|
+
echo "Updating baseline screenshots..."
|
|
114
|
+
node scripts/visual-regression.cjs update-baseline \
|
|
115
|
+
"${{ env.SCREENSHOTS_DIR }}" \
|
|
116
|
+
"${{ env.BASELINE_DIR }}"
|
|
117
|
+
|
|
118
|
+
- name: Upload Current Screenshots
|
|
119
|
+
uses: actions/upload-artifact@v4
|
|
120
|
+
with:
|
|
121
|
+
name: visual-screenshots-${{ github.run_number }}
|
|
122
|
+
path: ${{ env.SCREENSHOTS_DIR }}
|
|
123
|
+
retention-days: 30
|
|
124
|
+
|
|
125
|
+
- name: Upload Diff Images
|
|
126
|
+
if: ${{ !inputs.update_baseline && steps.comparison.outputs.report_exists == 'true' }}
|
|
127
|
+
uses: actions/upload-artifact@v4
|
|
128
|
+
with:
|
|
129
|
+
name: visual-diff-${{ github.run_number }}
|
|
130
|
+
path: ${{ env.DIFF_DIR }}
|
|
131
|
+
retention-days: 30
|
|
132
|
+
if-no-files-found: ignore
|
|
133
|
+
|
|
134
|
+
- name: Upload Baseline (for future comparisons)
|
|
135
|
+
if: ${{ inputs.update_baseline || steps.comparison.outputs.no_baseline == 'true' }}
|
|
136
|
+
uses: actions/upload-artifact@v4
|
|
137
|
+
with:
|
|
138
|
+
name: visual-baseline
|
|
139
|
+
path: ${{ env.BASELINE_DIR }}
|
|
140
|
+
retention-days: 90
|
|
141
|
+
|
|
142
|
+
- name: Upload Regression Report
|
|
143
|
+
if: ${{ !inputs.update_baseline }}
|
|
144
|
+
uses: actions/upload-artifact@v4
|
|
145
|
+
with:
|
|
146
|
+
name: visual-regression-report-${{ github.run_number }}
|
|
147
|
+
path: visual-regression-report.md
|
|
148
|
+
retention-days: 30
|
|
149
|
+
if-no-files-found: ignore
|
|
150
|
+
|
|
151
|
+
- name: Comment on PR (if applicable)
|
|
152
|
+
if: github.event_name == 'pull_request' && !inputs.update_baseline
|
|
153
|
+
uses: actions/github-script@v7
|
|
154
|
+
with:
|
|
155
|
+
script: |
|
|
156
|
+
const fs = require('fs');
|
|
157
|
+
|
|
158
|
+
let report = '## 📸 Visual Regression Report\n\n';
|
|
159
|
+
|
|
160
|
+
if (fs.existsSync('visual-regression-report.md')) {
|
|
161
|
+
report += fs.readFileSync('visual-regression-report.md', 'utf8');
|
|
162
|
+
} else {
|
|
163
|
+
report += 'No visual regression report generated.\n';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
report += `\n\n### 📎 Artifacts\n`;
|
|
167
|
+
report += `- [Screenshots](../actions/runs/${{ github.run_id }})\n`;
|
|
168
|
+
report += `- [Diff Images](../actions/runs/${{ github.run_id }})\n`;
|
|
169
|
+
|
|
170
|
+
report += '\n---\n*Visual regression testing by qai*';
|
|
171
|
+
|
|
172
|
+
await github.rest.issues.createComment({
|
|
173
|
+
owner: context.repo.owner,
|
|
174
|
+
repo: context.repo.repo,
|
|
175
|
+
issue_number: context.issue.number,
|
|
176
|
+
body: report
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
- name: Summary
|
|
180
|
+
run: |
|
|
181
|
+
echo "## Visual Regression Summary" >> $GITHUB_STEP_SUMMARY
|
|
182
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
183
|
+
|
|
184
|
+
if [ -f "visual-regression-report.md" ]; then
|
|
185
|
+
cat visual-regression-report.md >> $GITHUB_STEP_SUMMARY
|
|
186
|
+
elif [ "${{ inputs.update_baseline }}" == "true" ]; then
|
|
187
|
+
echo "✅ Baseline screenshots updated successfully" >> $GITHUB_STEP_SUMMARY
|
|
188
|
+
else
|
|
189
|
+
echo "📸 Screenshots captured - no baseline to compare against" >> $GITHUB_STEP_SUMMARY
|
|
190
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
191
|
+
echo "Run this workflow again with 'Update baseline screenshots' checked to create a baseline." >> $GITHUB_STEP_SUMMARY
|
|
192
|
+
fi
|
package/.prettierrc.json
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# qai
|
|
2
|
+
|
|
3
|
+
AI-powered QA engineer for your terminal. Scan websites, review PRs, generate tests.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/qai-cli)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/tyler-james-bridges/qaie/actions/workflows/lint.yml)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g qai-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### `qai scan` — Visual QA Analysis
|
|
18
|
+
|
|
19
|
+
Capture screenshots, detect console/network errors, and get AI-powered bug reports.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Scan a URL
|
|
23
|
+
qai scan https://mysite.com
|
|
24
|
+
|
|
25
|
+
# Multiple viewports
|
|
26
|
+
VIEWPORTS=desktop,mobile,tablet qai scan https://mysite.com
|
|
27
|
+
|
|
28
|
+
# Focus on accessibility
|
|
29
|
+
FOCUS=accessibility qai scan https://mysite.com
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `qai review` — PR Code Review _(Coming Soon)_
|
|
33
|
+
|
|
34
|
+
Deep code review with full codebase context. Not just the diff — traces through dependencies, callers, and related tests.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Review a PR
|
|
38
|
+
qai review 42
|
|
39
|
+
|
|
40
|
+
# Review current branch against main
|
|
41
|
+
qai review --base main
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `qai generate` — Test Generation _(Coming Soon)_
|
|
45
|
+
|
|
46
|
+
Auto-generate Playwright E2E tests from URLs or unit tests from source files.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Generate E2E tests by crawling a site
|
|
50
|
+
qai generate https://mysite.com
|
|
51
|
+
|
|
52
|
+
# Generate unit tests from source
|
|
53
|
+
qai generate src/billing.ts
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Playwright Integration
|
|
57
|
+
|
|
58
|
+
Use qai inside your existing Playwright test suite:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { test, expect } from '@playwright/test';
|
|
62
|
+
import { analyzeWithAI, attachScreenshots } from 'qai-cli';
|
|
63
|
+
|
|
64
|
+
test('homepage has no critical issues', async ({ page }, testInfo) => {
|
|
65
|
+
await page.goto('/');
|
|
66
|
+
|
|
67
|
+
const report = await analyzeWithAI(page, {
|
|
68
|
+
viewports: ['desktop', 'mobile'],
|
|
69
|
+
focus: 'all',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await attachScreenshots(testInfo, report);
|
|
73
|
+
expect(report.criticalBugs).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## GitHub Action
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
- name: QAI Scan
|
|
81
|
+
uses: tyler-james-bridges/qaie@main
|
|
82
|
+
with:
|
|
83
|
+
url: ${{ env.PREVIEW_URL }}
|
|
84
|
+
viewports: desktop,mobile
|
|
85
|
+
env:
|
|
86
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## AI Providers
|
|
90
|
+
|
|
91
|
+
Works with any major LLM. Set one env var:
|
|
92
|
+
|
|
93
|
+
| Provider | Env Var | Default Model |
|
|
94
|
+
| --------- | ------------------- | --------------- |
|
|
95
|
+
| Anthropic | `ANTHROPIC_API_KEY` | claude-sonnet-4 |
|
|
96
|
+
| OpenAI | `OPENAI_API_KEY` | gpt-4o |
|
|
97
|
+
| Google | `GEMINI_API_KEY` | gemini-pro |
|
|
98
|
+
| Ollama | `OLLAMA_HOST` | llama3 |
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
- **Multi-viewport** — Desktop, tablet, mobile screenshots
|
|
103
|
+
- **Console errors** — JavaScript errors and warnings
|
|
104
|
+
- **Network errors** — Failed APIs, slow requests, 4xx/5xx
|
|
105
|
+
- **Visual regression** — Pixel-level comparison with baselines
|
|
106
|
+
- **Structured reports** — JSON + Markdown output
|
|
107
|
+
- **CI/CD ready** — GitHub Action + exit codes for pipelines
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
package/action.yml
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
name: 'qai'
|
|
2
|
+
description: 'AI-powered QA engineer. Code review, testing, and bug detection. Supports multiple LLM providers.'
|
|
3
|
+
author: 'tyler-james-bridges'
|
|
4
|
+
|
|
5
|
+
branding:
|
|
6
|
+
icon: 'check-circle'
|
|
7
|
+
color: 'green'
|
|
8
|
+
|
|
9
|
+
inputs:
|
|
10
|
+
url:
|
|
11
|
+
description: 'URL to test'
|
|
12
|
+
required: true
|
|
13
|
+
|
|
14
|
+
# Provider selection (auto-detected if not specified)
|
|
15
|
+
provider:
|
|
16
|
+
description: 'LLM provider: anthropic, openai, codex, gemini, ollama'
|
|
17
|
+
required: false
|
|
18
|
+
|
|
19
|
+
# Provider-specific API keys (pass whichever you have)
|
|
20
|
+
anthropic_api_key:
|
|
21
|
+
description: 'Anthropic API key (for Claude)'
|
|
22
|
+
required: false
|
|
23
|
+
openai_api_key:
|
|
24
|
+
description: 'OpenAI API key (for GPT-4)'
|
|
25
|
+
required: false
|
|
26
|
+
codex_api_key:
|
|
27
|
+
description: 'Codex API key'
|
|
28
|
+
required: false
|
|
29
|
+
gemini_api_key:
|
|
30
|
+
description: 'Google Gemini API key'
|
|
31
|
+
required: false
|
|
32
|
+
|
|
33
|
+
# Generic key (use with provider input)
|
|
34
|
+
api_key:
|
|
35
|
+
description: 'Generic API key (use with provider input)'
|
|
36
|
+
required: false
|
|
37
|
+
|
|
38
|
+
# Ollama settings (for local models)
|
|
39
|
+
ollama_base_url:
|
|
40
|
+
description: 'Ollama base URL (default: http://localhost:11434)'
|
|
41
|
+
required: false
|
|
42
|
+
default: 'http://localhost:11434'
|
|
43
|
+
ollama_model:
|
|
44
|
+
description: 'Ollama model to use (default: llava)'
|
|
45
|
+
required: false
|
|
46
|
+
default: 'llava'
|
|
47
|
+
|
|
48
|
+
# Test configuration
|
|
49
|
+
viewports:
|
|
50
|
+
description: 'Viewports to test (comma-separated): desktop,mobile,tablet'
|
|
51
|
+
required: false
|
|
52
|
+
default: 'desktop,mobile'
|
|
53
|
+
focus:
|
|
54
|
+
description: 'Focus area: all, accessibility, performance, forms, visual'
|
|
55
|
+
required: false
|
|
56
|
+
default: 'all'
|
|
57
|
+
timeout:
|
|
58
|
+
description: 'Timeout in seconds'
|
|
59
|
+
required: false
|
|
60
|
+
default: '300'
|
|
61
|
+
|
|
62
|
+
# Output options
|
|
63
|
+
fail_on_bugs:
|
|
64
|
+
description: 'Fail workflow if critical/high severity bugs found'
|
|
65
|
+
required: false
|
|
66
|
+
default: 'false'
|
|
67
|
+
output_format:
|
|
68
|
+
description: 'Report format: markdown, json, sarif'
|
|
69
|
+
required: false
|
|
70
|
+
default: 'markdown'
|
|
71
|
+
|
|
72
|
+
outputs:
|
|
73
|
+
report:
|
|
74
|
+
description: 'Path to the QA report file'
|
|
75
|
+
screenshots:
|
|
76
|
+
description: 'Path to screenshots directory'
|
|
77
|
+
bugs_found:
|
|
78
|
+
description: 'Number of bugs found'
|
|
79
|
+
critical_bugs:
|
|
80
|
+
description: 'Number of critical/high severity bugs'
|
|
81
|
+
|
|
82
|
+
runs:
|
|
83
|
+
using: 'composite'
|
|
84
|
+
steps:
|
|
85
|
+
- name: Setup Node.js
|
|
86
|
+
uses: actions/setup-node@v4
|
|
87
|
+
with:
|
|
88
|
+
node-version: '20'
|
|
89
|
+
|
|
90
|
+
- name: Install dependencies
|
|
91
|
+
shell: bash
|
|
92
|
+
run: |
|
|
93
|
+
cd ${{ github.action_path }}
|
|
94
|
+
npm ci
|
|
95
|
+
|
|
96
|
+
- name: Install Playwright
|
|
97
|
+
shell: bash
|
|
98
|
+
run: npx playwright install chromium --with-deps
|
|
99
|
+
|
|
100
|
+
- name: Run QA Tests
|
|
101
|
+
id: qa-test
|
|
102
|
+
shell: bash
|
|
103
|
+
env:
|
|
104
|
+
INPUT_URL: ${{ inputs.url }}
|
|
105
|
+
INPUT_PROVIDER: ${{ inputs.provider }}
|
|
106
|
+
INPUT_ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
|
|
107
|
+
INPUT_OPENAI_API_KEY: ${{ inputs.openai_api_key }}
|
|
108
|
+
INPUT_CODEX_API_KEY: ${{ inputs.codex_api_key }}
|
|
109
|
+
INPUT_GEMINI_API_KEY: ${{ inputs.gemini_api_key }}
|
|
110
|
+
INPUT_API_KEY: ${{ inputs.api_key }}
|
|
111
|
+
INPUT_OLLAMA_BASE_URL: ${{ inputs.ollama_base_url }}
|
|
112
|
+
INPUT_OLLAMA_MODEL: ${{ inputs.ollama_model }}
|
|
113
|
+
INPUT_VIEWPORTS: ${{ inputs.viewports }}
|
|
114
|
+
INPUT_FOCUS: ${{ inputs.focus }}
|
|
115
|
+
INPUT_TIMEOUT: ${{ inputs.timeout }}
|
|
116
|
+
INPUT_OUTPUT_FORMAT: ${{ inputs.output_format }}
|
|
117
|
+
run: |
|
|
118
|
+
cd ${{ github.action_path }}
|
|
119
|
+
node src/index.js
|
|
120
|
+
|
|
121
|
+
- name: Upload Screenshots
|
|
122
|
+
uses: actions/upload-artifact@v4
|
|
123
|
+
if: always()
|
|
124
|
+
with:
|
|
125
|
+
name: qa-screenshots-${{ github.run_number }}
|
|
126
|
+
path: screenshots/
|
|
127
|
+
retention-days: 14
|
|
128
|
+
if-no-files-found: ignore
|
|
129
|
+
|
|
130
|
+
- name: Upload Report
|
|
131
|
+
uses: actions/upload-artifact@v4
|
|
132
|
+
if: always()
|
|
133
|
+
with:
|
|
134
|
+
name: qa-report-${{ github.run_number }}
|
|
135
|
+
path: qa-report.*
|
|
136
|
+
retention-days: 14
|
|
137
|
+
if-no-files-found: ignore
|
|
138
|
+
|
|
139
|
+
- name: Check for critical bugs
|
|
140
|
+
if: ${{ inputs.fail_on_bugs == 'true' }}
|
|
141
|
+
shell: bash
|
|
142
|
+
run: |
|
|
143
|
+
if [ -f "qa-report.json" ]; then
|
|
144
|
+
CRITICAL=$(cat qa-report.json | jq '.bugs | map(select(.severity == "critical" or .severity == "high")) | length')
|
|
145
|
+
if [ "$CRITICAL" -gt 0 ]; then
|
|
146
|
+
echo "::error::Found $CRITICAL critical/high severity bugs"
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
fi
|
package/docs/BUGS.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Intentional Bugs in Demo Site
|
|
2
|
+
|
|
3
|
+
This site contains intentional bugs for testing qai.
|
|
4
|
+
|
|
5
|
+
| Bug | Category | Severity | Location |
|
|
6
|
+
| -------------------------------------- | ------------- | -------- | ------------------------ |
|
|
7
|
+
| Broken "Resources" link | Functional | High | index.html:14 |
|
|
8
|
+
| Missing alt text on hero image | Accessibility | Medium | index.html:19 |
|
|
9
|
+
| Hero image 404 | Network | Medium | index.html:19 |
|
|
10
|
+
| Mobile nav doesn't work | Functional | High | style.css:186, app.js:56 |
|
|
11
|
+
| Horizontal scroll on mobile | Visual | Medium | style.css:79 |
|
|
12
|
+
| Missing focus styles | Accessibility | Medium | style.css:197 |
|
|
13
|
+
| Form has no validation | Functional | Medium | index.html:75-82 |
|
|
14
|
+
| Email input wrong type | Accessibility | Low | index.html:79 |
|
|
15
|
+
| Console error: undefined siteConfig | JavaScript | Medium | app.js:7 |
|
|
16
|
+
| Console error: undefined trackPurchase | JavaScript | Medium | app.js:51 |
|
|
17
|
+
| Console error: undefined analytics | JavaScript | Low | app.js:71 |
|
|
18
|
+
| No required fields on form | Functional | Medium | index.html:75-82 |
|
|
19
|
+
| Disabled button with no explanation | UX | Low | index.html:66 |
|
|
20
|
+
| Scroll handler not throttled | Performance | Low | app.js:62 |
|
|
21
|
+
| Generic form error message | UX | Low | app.js:41 |
|
|
22
|
+
|
|
23
|
+
## Expected QA Findings
|
|
24
|
+
|
|
25
|
+
A thorough QA test should find:
|
|
26
|
+
|
|
27
|
+
- **3+** console errors
|
|
28
|
+
- **2+** accessibility issues
|
|
29
|
+
- **2+** mobile/responsive issues
|
|
30
|
+
- **1** broken link
|
|
31
|
+
- **1** 404 resource
|
|
32
|
+
|
|
33
|
+
## Testing This Site
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Via GitHub Actions
|
|
37
|
+
Go to Actions → qai → Run workflow
|
|
38
|
+
URL: https://tyler-james-bridges.github.io/qaie/
|
|
39
|
+
|
|
40
|
+
# Via Visual Regression
|
|
41
|
+
Go to Actions → Visual Regression Testing → Run workflow
|
|
42
|
+
URL: https://tyler-james-bridges.github.io/qaie/
|
|
43
|
+
```
|
package/docs/app.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// qai Demo Site - JavaScript
|
|
2
|
+
// This file contains intentional bugs for QA testing
|
|
3
|
+
|
|
4
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
5
|
+
// BUG: Referencing undefined variable causes console error
|
|
6
|
+
console.log('Demo site loaded');
|
|
7
|
+
console.log('Config:', siteConfig); // ReferenceError: siteConfig is not defined
|
|
8
|
+
|
|
9
|
+
// CTA Button click handler
|
|
10
|
+
const ctaBtn = document.getElementById('cta-btn');
|
|
11
|
+
if (ctaBtn) {
|
|
12
|
+
ctaBtn.addEventListener('click', function () {
|
|
13
|
+
// BUG: Alert is not accessible and annoying
|
|
14
|
+
alert('Thanks for clicking! This is a demo site.');
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Form submission handler
|
|
19
|
+
const form = document.getElementById('contact-form');
|
|
20
|
+
const statusEl = document.getElementById('form-status');
|
|
21
|
+
|
|
22
|
+
if (form) {
|
|
23
|
+
form.addEventListener('submit', function (e) {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
|
|
26
|
+
const name = document.getElementById('name').value;
|
|
27
|
+
const email = document.getElementById('email').value;
|
|
28
|
+
const message = document.getElementById('message').value;
|
|
29
|
+
|
|
30
|
+
// BUG: No client-side validation before "sending"
|
|
31
|
+
// BUG: Race condition - status shows before "processing" completes
|
|
32
|
+
statusEl.textContent = 'Sending...';
|
|
33
|
+
statusEl.className = 'form-status';
|
|
34
|
+
|
|
35
|
+
// Simulate API call
|
|
36
|
+
setTimeout(function () {
|
|
37
|
+
// BUG: Always succeeds, never validates email format
|
|
38
|
+
if (name && email && message) {
|
|
39
|
+
statusEl.textContent = 'Message sent successfully!';
|
|
40
|
+
statusEl.className = 'form-status success';
|
|
41
|
+
form.reset();
|
|
42
|
+
} else {
|
|
43
|
+
// BUG: Generic error message doesn't tell user what's missing
|
|
44
|
+
statusEl.textContent = 'Error: Please fill out the form.';
|
|
45
|
+
statusEl.className = 'form-status error';
|
|
46
|
+
}
|
|
47
|
+
}, 1000);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Buy button handlers
|
|
52
|
+
const buyButtons = document.querySelectorAll('.buy-btn');
|
|
53
|
+
buyButtons.forEach(function (btn) {
|
|
54
|
+
btn.addEventListener('click', function () {
|
|
55
|
+
if (!btn.disabled) {
|
|
56
|
+
// BUG: Uses deprecated API
|
|
57
|
+
const plan = btn.parentElement.querySelector('h3').innerText;
|
|
58
|
+
console.log('Selected plan:', plan);
|
|
59
|
+
|
|
60
|
+
// BUG: Another console error - undefined function
|
|
61
|
+
trackPurchase(plan); // ReferenceError: trackPurchase is not defined
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Mobile nav toggle
|
|
67
|
+
const navToggle = document.querySelector('.nav-toggle');
|
|
68
|
+
const navLinks = document.querySelector('.nav-links');
|
|
69
|
+
|
|
70
|
+
if (navToggle) {
|
|
71
|
+
navToggle.addEventListener('click', function () {
|
|
72
|
+
// BUG: Toggle doesn't actually work - CSS hides nav-links on mobile with no toggle class
|
|
73
|
+
navLinks.classList.toggle('active');
|
|
74
|
+
console.log('Nav toggled, but CSS does not support this');
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// BUG: Scroll event listener without throttling (performance issue)
|
|
79
|
+
window.addEventListener('scroll', function () {
|
|
80
|
+
const scrollPos = window.scrollY;
|
|
81
|
+
// Doing unnecessary work on every scroll event
|
|
82
|
+
document.querySelectorAll('.feature-card').forEach(function (card, index) {
|
|
83
|
+
card.style.opacity = Math.min(1, scrollPos / 500 + 0.5);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// BUG: Set timeout that logs after page might be navigated away
|
|
88
|
+
setTimeout(function () {
|
|
89
|
+
console.log('Delayed analytics ping');
|
|
90
|
+
// BUG: Trying to call undefined analytics
|
|
91
|
+
analytics.track('page_view_delayed'); // ReferenceError
|
|
92
|
+
}, 5000);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// BUG: Global error handler that swallows errors (bad practice)
|
|
96
|
+
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|
97
|
+
console.log('Error caught:', msg);
|
|
98
|
+
// BUG: Returns true which prevents error from being logged to console properly
|
|
99
|
+
// return true; // Uncomment to hide all errors (worse bug)
|
|
100
|
+
return false;
|
|
101
|
+
};
|