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.
@@ -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
@@ -0,0 +1,10 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "all",
4
+ "singleQuote": true,
5
+ "printWidth": 100,
6
+ "tabWidth": 2,
7
+ "useTabs": false,
8
+ "arrowParens": "always",
9
+ "endOfLine": "lf"
10
+ }
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
+ [![npm version](https://img.shields.io/npm/v/qai-cli)](https://www.npmjs.com/package/qai-cli)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![Lint](https://github.com/tyler-james-bridges/qaie/actions/workflows/lint.yml/badge.svg)](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
+ };