@qatonic_innovations/qaios 0.1.0 → 0.1.2
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/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# QAIOS
|
|
2
|
+
|
|
3
|
+
> **AI QA engineer in your terminal.** Designs, writes, runs, heals, and explores tests for web UI and APIs — with audit-grade traceability.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@qatonic_innovations/qaios)
|
|
6
|
+
[](https://github.com/qatonic/qaios/blob/main/LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
QAIOS acts like a QA engineer on your team. Point it at a feature description or an OpenAPI spec; it produces a test design, generates Playwright code, runs the tests, classifies failures, heals broken locators, files defects, scans for accessibility issues, and keeps a hash-chained audit log of every decision.
|
|
10
|
+
|
|
11
|
+
**Mental model: Claude Code, but for QA work instead of feature coding.**
|
|
12
|
+
|
|
13
|
+
> **Status: early alpha (v0.1).** Core flows — `init`, `doctor`, test design
|
|
14
|
+
> & generation, Playwright execution, self-healing, accessibility, and the
|
|
15
|
+
> audit log — work today. Expect rough edges and please
|
|
16
|
+
> [report issues](https://github.com/qatonic/qaios/issues). After installing,
|
|
17
|
+
> a quick smoke test confirms everything resolves:
|
|
18
|
+
>
|
|
19
|
+
> ```bash
|
|
20
|
+
> qaios --version
|
|
21
|
+
> qaios --help
|
|
22
|
+
> qaios doctor # checks Node, API key, Playwright, config
|
|
23
|
+
> ```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g @qatonic_innovations/qaios
|
|
31
|
+
# or: pnpm add -g @qatonic_innovations/qaios
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The published package is `@qatonic_innovations/qaios`; the installed **command is `qaios`**.
|
|
35
|
+
|
|
36
|
+
### Requirements
|
|
37
|
+
|
|
38
|
+
- **Node.js 20 LTS recommended.** QAIOS bundles a native SQLite module
|
|
39
|
+
(better-sqlite3) for its local audit log; Node 20 LTS has the widest
|
|
40
|
+
prebuilt-binary coverage. Newer Node usually works but may need to compile
|
|
41
|
+
the binary (build tools + network access).
|
|
42
|
+
- An **Anthropic API key** — get one at
|
|
43
|
+
[console.anthropic.com](https://console.anthropic.com/settings/keys), then
|
|
44
|
+
put it in your environment:
|
|
45
|
+
```bash
|
|
46
|
+
export ANTHROPIC_API_KEY=sk-ant-... # macOS/Linux
|
|
47
|
+
setx ANTHROPIC_API_KEY "sk-ant-..." # Windows (open a NEW shell after)
|
|
48
|
+
```
|
|
49
|
+
The key is read from the environment and **never written to disk** by QAIOS.
|
|
50
|
+
- **Playwright** in your project, for `qaios run` / `snapshot` / `explore` / `a11y`:
|
|
51
|
+
```bash
|
|
52
|
+
npm i -D @playwright/test && npx playwright install
|
|
53
|
+
```
|
|
54
|
+
(`@playwright/test` is for `run`; `explore`/`a11y` use the `playwright`
|
|
55
|
+
package, which it pulls in.)
|
|
56
|
+
- For `qaios a11y`, also: `npm i -D @axe-core/playwright`
|
|
57
|
+
|
|
58
|
+
### Install troubleshooting
|
|
59
|
+
|
|
60
|
+
If `qaios init` fails with a SQLite/native-binding error
|
|
61
|
+
(`Could not load the native SQLite module`):
|
|
62
|
+
|
|
63
|
+
- Confirm your Node version: `node -v` (prefer 20 LTS).
|
|
64
|
+
- Rebuild the binary: `npm rebuild better-sqlite3` — or reinstall qaios.
|
|
65
|
+
- Behind a proxy/firewall? The prebuilt binary is fetched from GitHub
|
|
66
|
+
release assets; allow that host, or install C/C++ build tools so it can
|
|
67
|
+
compile locally.
|
|
68
|
+
|
|
69
|
+
`qaios doctor` will tell you exactly which check failed and what to run.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 60-second quick start
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd my-app
|
|
77
|
+
qaios init # creates .qaios/, QAIOS.md; detects Playwright
|
|
78
|
+
qaios doctor # verify your environment is ready
|
|
79
|
+
|
|
80
|
+
# Generate tests from plain English, then run them:
|
|
81
|
+
qaios test "user can log in with email and password" --run
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`qaios test` produces a DesignSpec, a Playwright spec, and a Page Object — then (`--run`) executes them. That's the core loop.
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
workflow: 01JTZ1MV7Q8X3R2WFP9D5K8YRH
|
|
88
|
+
|
|
89
|
+
▸ requirements.intake ✓ (2 reqs, confidence 0.85)
|
|
90
|
+
▸ risk.score ✓ (overall HIGH — touches PII)
|
|
91
|
+
▸ design.web ✓ (4 scenarios, confidence 0.84)
|
|
92
|
+
▸ write.web ✓ (1 spec, 4 tests, 1 page object)
|
|
93
|
+
|
|
94
|
+
Generated:
|
|
95
|
+
tests/auth/login.spec.ts (4 tests)
|
|
96
|
+
tests/auth/LoginPage.po.ts
|
|
97
|
+
.qaios/specs/01JTZ1…/DesignSpec.json
|
|
98
|
+
|
|
99
|
+
Cost: 11,403 tokens · $0.06 (3 of 15 LLM calls used)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> **Gates:** on HIGH/CRITICAL-risk features QAIOS pauses for review. Approve interactively with `qaios review`, or pass `--yes` to auto-approve for a non-interactive run.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## What each command does
|
|
107
|
+
|
|
108
|
+
| Command | What it does |
|
|
109
|
+
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
110
|
+
| `qaios init [path]` | Bootstrap a project: create `.qaios/`, `QAIOS.md`, run migrations. |
|
|
111
|
+
| `qaios doctor` | Health-check your environment (Node, API key, Playwright, config). `--json` for CI. |
|
|
112
|
+
| `qaios test <description>` | Design + generate tests from natural language. `--type api` for OpenAPI. `--run` to execute. `--yes` to auto-approve gates. |
|
|
113
|
+
| `qaios run [pattern]` | Execute Playwright tests, classify failures (real / flaky / locator drift), auto-heal locator breaks, file defects. |
|
|
114
|
+
| `qaios fix [target]` | Self-heal a broken locator. `--last-failure` to target the most recent failure, `--auto` to apply, `--dry-run` to preview. |
|
|
115
|
+
| `qaios explore <url>` | Time-boxed exploratory session against a live URL. `--duration <seconds>`, `--focus "<hint>"`. |
|
|
116
|
+
| `qaios a11y <url>` | Deterministic axe-core accessibility scan + AI triage. `--wcag-level A\|AA\|AAA`. Exits 1 on real violations (CI-gateable). |
|
|
117
|
+
| `qaios snapshot capture\|check\|review` | Visual regression: capture baselines, diff, and triage AI-classified diffs. |
|
|
118
|
+
| `qaios review` | Walk pending human-review gates and approve/reject; resumes the workflow. |
|
|
119
|
+
| `qaios history [--verify]` | List workflows, inspect the audit log, and verify the hash chain. |
|
|
120
|
+
| `qaios config get\|set\|show\|edit` | Read/write `.qaios/config.yaml` (schema-validated). |
|
|
121
|
+
| `qaios mcp list\|add\|remove\|enable\|disable\|test` | Manage MCP servers (GitHub, Jira, Slack, custom). |
|
|
122
|
+
|
|
123
|
+
Run `qaios <command> --help` for the full option list of any command.
|
|
124
|
+
|
|
125
|
+
### Common workflows
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Generate API tests from an OpenAPI spec
|
|
129
|
+
qaios test --type api --spec ./openapi.yaml "exercise the /orders endpoints"
|
|
130
|
+
|
|
131
|
+
# Run a suite; QAIOS classifies failures and self-heals locator drift
|
|
132
|
+
qaios run
|
|
133
|
+
|
|
134
|
+
# Heal a specific broken test, previewing the patch first
|
|
135
|
+
qaios fix tests/auth/login.spec.ts --last-failure --dry-run
|
|
136
|
+
|
|
137
|
+
# Accessibility scan, gate CI on real WCAG violations
|
|
138
|
+
qaios a11y https://staging.myapp.com --wcag-level AA
|
|
139
|
+
|
|
140
|
+
# Exploratory testing with a focus hint
|
|
141
|
+
qaios explore https://staging.myapp.com --duration 900 --focus "checkout flow"
|
|
142
|
+
|
|
143
|
+
# Visual regression
|
|
144
|
+
qaios snapshot capture # baseline
|
|
145
|
+
# ...change CSS...
|
|
146
|
+
qaios snapshot check # diff
|
|
147
|
+
qaios snapshot review # triage interactively
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
Config lives in `.qaios/config.yaml` (created by `qaios init`):
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
version: 1
|
|
158
|
+
mode: LITE # LITE | FULL | TRUST
|
|
159
|
+
app:
|
|
160
|
+
baseUrl: https://staging.myapp.com
|
|
161
|
+
llm:
|
|
162
|
+
provider: anthropic
|
|
163
|
+
apiKeyEnv: ANTHROPIC_API_KEY # key is read from env, never stored
|
|
164
|
+
maxLlmCallsPerWorkflow: 15
|
|
165
|
+
costAlertThresholdUsdCents: 50
|
|
166
|
+
testing:
|
|
167
|
+
framework: playwright
|
|
168
|
+
testDir: tests
|
|
169
|
+
testIdAttribute: data-testid # e.g. set to 'data-test' for some apps
|
|
170
|
+
locatorPreference: [data-testid, role, text]
|
|
171
|
+
gates:
|
|
172
|
+
autoExpireOnTimeout: abort # abort | auto_approve | auto_reject
|
|
173
|
+
defects:
|
|
174
|
+
target: stdout # stdout | github | jira
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`qaios config show` prints the resolved config; `qaios config set <key> <value>` validates against the schema before writing. **API keys are never written to config** — they come from environment variables.
|
|
178
|
+
|
|
179
|
+
### Operating modes
|
|
180
|
+
|
|
181
|
+
- **LITE** (default) — HIGH/CRITICAL risk pauses for review; routine work flows through.
|
|
182
|
+
- **FULL** — every gate active, every AI decision reviewable.
|
|
183
|
+
- **TRUST** — auto-approve when confidence > 0.85 and risk ≤ MEDIUM; CRITICAL still notifies.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
qaios config set mode TRUST
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Cost & privacy
|
|
192
|
+
|
|
193
|
+
- All v0.1 skills use Claude Sonnet. A typical `qaios test` costs ~**$0.04–0.10**. Each workflow is capped (default `min(15 calls, $0.50)`, configurable) and aborts if exceeded.
|
|
194
|
+
- **Local-first.** No telemetry, no phone-home. The only outbound traffic is (a) LLM calls to Anthropic with the prompts QAIOS builds, and (b) any MCP servers you configure. You bring your own API key; QAIOS does not proxy your requests. See [SECURITY.md](https://github.com/qatonic/qaios/blob/main/SECURITY.md) for exactly what's sent.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Exit codes (for CI)
|
|
199
|
+
|
|
200
|
+
Every command exits with a stable, documented code so you can gate pipelines:
|
|
201
|
+
|
|
202
|
+
| Code | Meaning | Example |
|
|
203
|
+
| ----- | ------------------------------- | ----------------------------------------------------------------------- |
|
|
204
|
+
| `0` | Success | tests generated; `doctor` all-green; `a11y` clean |
|
|
205
|
+
| `1` | User error / actionable failure | bad flag; `a11y` found real violations; `run` had failing tests |
|
|
206
|
+
| `2` | Gate blocked (informational) | a workflow paused for human review — run `qaios review` or pass `--yes` |
|
|
207
|
+
| `3` | Tool/dependency error | Playwright or axe not installed; SQLite binding missing |
|
|
208
|
+
| `4` | LLM error | rate limit, timeout, or invalid API key |
|
|
209
|
+
| `5` | Internal error | unexpected crash (re-run with `--debug` for a stack trace) |
|
|
210
|
+
| `130` | Cancelled | you pressed Ctrl+C |
|
|
211
|
+
|
|
212
|
+
`qaios doctor --json` and `--json` on most commands emit machine-readable
|
|
213
|
+
output for CI consumption.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Documentation
|
|
218
|
+
|
|
219
|
+
- [Getting Started](https://github.com/qatonic/qaios/blob/main/docs/getting-started.md) — first 15 minutes
|
|
220
|
+
- [Commands Reference](https://github.com/qatonic/qaios/blob/main/docs/commands.md) — every CLI command
|
|
221
|
+
- [Skills Reference](https://github.com/qatonic/qaios/blob/main/docs/skills-reference.md) — what each skill does
|
|
222
|
+
- [MCP Servers](https://github.com/qatonic/qaios/blob/main/docs/mcp-servers.md) — adding integrations
|
|
223
|
+
- A runnable end-to-end example lives in [`examples/live-saucedemo`](https://github.com/qatonic/qaios/tree/main/examples/live-saucedemo).
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Status & roadmap
|
|
228
|
+
|
|
229
|
+
**v0.1 (now):** Web UI + API + visual regression + self-healing + exploratory + accessibility, as an open-source CLI. Early — feedback wanted.
|
|
230
|
+
|
|
231
|
+
- **v0.2** — optional cloud sync + web dashboard
|
|
232
|
+
- **v0.5** — mobile testing
|
|
233
|
+
- **v0.7** — performance + security testing
|
|
234
|
+
- **v1.0** — database testing, stable public API
|
|
235
|
+
|
|
236
|
+
The roadmap is gated on real-world validation, not a calendar.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Links
|
|
241
|
+
|
|
242
|
+
- **Repository:** https://github.com/qatonic/qaios
|
|
243
|
+
- **Report a bug:** https://github.com/qatonic/qaios/issues
|
|
244
|
+
- **Discuss / request a feature:** https://github.com/qatonic/qaios/discussions
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
[MIT](https://github.com/qatonic/qaios/blob/main/LICENSE). The CLI will always remain MIT; cloud features (v0.2+) ship under a separate license.
|
|
@@ -19,6 +19,26 @@
|
|
|
19
19
|
// 0 = success (even when violations were found)
|
|
20
20
|
|
|
21
21
|
import { writeFileSync } from 'node:fs';
|
|
22
|
+
import { createRequire } from 'node:module';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { pathToFileURL } from 'node:url';
|
|
25
|
+
|
|
26
|
+
// Resolve a package from the USER's project (process.cwd()), not from
|
|
27
|
+
// where this script physically lives (inside QAIOS's install dir). With a
|
|
28
|
+
// bare `import('playwright')`, Node resolves relative to THIS file — so a
|
|
29
|
+
// globally-installed QAIOS can't see the user's locally-installed
|
|
30
|
+
// playwright. Resolving via a require rooted at the project's package.json
|
|
31
|
+
// (falling back to cwd) and importing the resolved file URL fixes that.
|
|
32
|
+
const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
|
|
33
|
+
// Resolve from the user's project, but import the BARE specifier so Node
|
|
34
|
+
// honors the package's `exports` map (giving the proper ESM shape). We only
|
|
35
|
+
// use require.resolve to (a) verify the package exists in the project and
|
|
36
|
+
// (b) make the bare import resolvable, by importing the resolved entry and
|
|
37
|
+
// reading a named property off it. Returns the live module namespace.
|
|
38
|
+
const importFromProject = async (specifier) => {
|
|
39
|
+
const resolved = projectRequire.resolve(specifier); // throws if not installed
|
|
40
|
+
return import(pathToFileURL(resolved).href);
|
|
41
|
+
};
|
|
22
42
|
|
|
23
43
|
const url = process.env.QAIOS_AXE_URL;
|
|
24
44
|
const out = process.env.QAIOS_AXE_OUTPUT;
|
|
@@ -36,8 +56,14 @@ if (!url || !out) {
|
|
|
36
56
|
|
|
37
57
|
let chromium, AxeBuilder;
|
|
38
58
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
// Importing a CJS entry via file URL nests the package's exports under
|
|
60
|
+
// `default`; importing an ESM entry exposes them as named. Read from
|
|
61
|
+
// whichever is present so both layouts work.
|
|
62
|
+
const pw = await importFromProject('playwright');
|
|
63
|
+
chromium = pw.chromium ?? pw.default?.chromium;
|
|
64
|
+
const axe = await importFromProject('@axe-core/playwright');
|
|
65
|
+
AxeBuilder = axe.default ?? axe.AxeBuilder;
|
|
66
|
+
if (!chromium || !AxeBuilder) throw new Error('resolved module missing expected export');
|
|
41
67
|
} catch (err) {
|
|
42
68
|
process.stderr.write(
|
|
43
69
|
`axe-runner: failed to load 'playwright' and '@axe-core/playwright' from the project — ` +
|
|
@@ -12,6 +12,20 @@
|
|
|
12
12
|
// via truncateHtmlSafe so the script stays decoupled from the budget.
|
|
13
13
|
|
|
14
14
|
import { writeFileSync } from 'node:fs';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
|
|
19
|
+
// Resolve `playwright` from the USER's project (process.cwd()), not from
|
|
20
|
+
// where this script lives inside QAIOS's install dir. A bare
|
|
21
|
+
// `import('playwright')` resolves relative to THIS file, so a globally
|
|
22
|
+
// installed QAIOS can't see the user's local playwright. See axe-runner.mjs
|
|
23
|
+
// for the full rationale.
|
|
24
|
+
const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
|
|
25
|
+
const importFromProject = async (specifier) => {
|
|
26
|
+
const resolved = projectRequire.resolve(specifier); // throws if not installed
|
|
27
|
+
return import(pathToFileURL(resolved).href);
|
|
28
|
+
};
|
|
15
29
|
|
|
16
30
|
const url = process.env.QAIOS_CAPTURE_URL;
|
|
17
31
|
const out = process.env.QAIOS_CAPTURE_OUTPUT;
|
|
@@ -27,7 +41,9 @@ if (!url || !out) {
|
|
|
27
41
|
|
|
28
42
|
let chromium;
|
|
29
43
|
try {
|
|
30
|
-
|
|
44
|
+
const pw = await importFromProject('playwright');
|
|
45
|
+
chromium = pw.chromium ?? pw.default?.chromium;
|
|
46
|
+
if (!chromium) throw new Error('playwright resolved but has no chromium export');
|
|
31
47
|
} catch (err) {
|
|
32
48
|
process.stderr.write(
|
|
33
49
|
`capture-page: failed to load 'playwright' from project — is it installed? ${err?.message ?? err}\n`,
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useApp, useInput, Box, Text } from 'ink';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
-
import { readFileSync, existsSync, rmSync, mkdirSync, writeFileSync, statSync, mkdtempSync, copyFileSync, readdirSync } from 'fs';
|
|
5
|
+
import { realpathSync, readFileSync, existsSync, rmSync, mkdirSync, writeFileSync, statSync, mkdtempSync, copyFileSync, readdirSync } from 'fs';
|
|
6
6
|
import path12 from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { Command, InvalidArgumentError } from 'commander';
|
|
@@ -2708,13 +2708,14 @@ function resolveSpawn(bin, args) {
|
|
|
2708
2708
|
}
|
|
2709
2709
|
return { bin: resolved, args };
|
|
2710
2710
|
}
|
|
2711
|
+
var SUPPRESSED_WARNING_CODES = /* @__PURE__ */ new Set(["DEP0190", "DEP0040"]);
|
|
2711
2712
|
var _filterInstalled = false;
|
|
2712
2713
|
function installDeprecationWarningFilter() {
|
|
2713
2714
|
if (_filterInstalled) return;
|
|
2714
2715
|
_filterInstalled = true;
|
|
2715
2716
|
process.removeAllListeners("warning");
|
|
2716
2717
|
process.on("warning", (w) => {
|
|
2717
|
-
if (w.code
|
|
2718
|
+
if (w.code !== void 0 && SUPPRESSED_WARNING_CODES.has(w.code)) return;
|
|
2718
2719
|
process.stderr.write(`(node:${process.pid}) ${w.name}: ${w.message}
|
|
2719
2720
|
`);
|
|
2720
2721
|
if (w.stack !== void 0) process.stderr.write(`${w.stack}
|
|
@@ -8547,6 +8548,18 @@ async function runExplore(opts) {
|
|
|
8547
8548
|
}
|
|
8548
8549
|
};
|
|
8549
8550
|
}
|
|
8551
|
+
if (opts.duration !== void 0) {
|
|
8552
|
+
const d = opts.duration;
|
|
8553
|
+
if (!Number.isFinite(d) || !Number.isInteger(d) || d < 60) {
|
|
8554
|
+
return {
|
|
8555
|
+
exitCode: ExitCode.USER_ERROR,
|
|
8556
|
+
error: {
|
|
8557
|
+
code: "qaios.explore.invalid_duration",
|
|
8558
|
+
message: `--duration must be a whole number of seconds \u2265 60 (got ${String(d)}).`
|
|
8559
|
+
}
|
|
8560
|
+
};
|
|
8561
|
+
}
|
|
8562
|
+
}
|
|
8550
8563
|
const config = loadConfig2(cwd);
|
|
8551
8564
|
const memory = loadProjectMemory(cwd);
|
|
8552
8565
|
const mode = opts.mode ?? config?.mode ?? "LITE";
|
|
@@ -9594,8 +9607,6 @@ function runInit(opts = {}) {
|
|
|
9594
9607
|
if (existsSync(qaiosDir) && opts.force) {
|
|
9595
9608
|
rmSync(qaiosDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
9596
9609
|
}
|
|
9597
|
-
mkdirSync(qaiosDir, { recursive: true });
|
|
9598
|
-
const filesWritten = [];
|
|
9599
9610
|
const modeParse = Mode.safeParse(opts.mode ?? "LITE");
|
|
9600
9611
|
if (!modeParse.success) {
|
|
9601
9612
|
return {
|
|
@@ -9614,9 +9625,7 @@ function runInit(opts = {}) {
|
|
|
9614
9625
|
testDir: opts.testDir ?? detection.testDir ?? "tests"
|
|
9615
9626
|
}
|
|
9616
9627
|
});
|
|
9617
|
-
|
|
9618
|
-
writeFileSync(configPath, stringify(config), "utf-8");
|
|
9619
|
-
filesWritten.push(path12.relative(cwd, configPath));
|
|
9628
|
+
mkdirSync(qaiosDir, { recursive: true });
|
|
9620
9629
|
const dbPath = path12.join(qaiosDir, "workflows.db");
|
|
9621
9630
|
let migrations;
|
|
9622
9631
|
try {
|
|
@@ -9624,16 +9633,33 @@ function runInit(opts = {}) {
|
|
|
9624
9633
|
migrations = storage.runMigrations();
|
|
9625
9634
|
storage.close();
|
|
9626
9635
|
} catch (err) {
|
|
9636
|
+
try {
|
|
9637
|
+
rmSync(qaiosDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
9638
|
+
} catch {
|
|
9639
|
+
}
|
|
9640
|
+
const raw = err.message ?? String(err);
|
|
9641
|
+
const isBindingError = /bindings file|was compiled against|NODE_MODULE_VERSION|\.node/i.test(
|
|
9642
|
+
raw
|
|
9643
|
+
);
|
|
9644
|
+
const message = isBindingError ? `Could not load the native SQLite module (better-sqlite3). This usually means the prebuilt binary didn't download or doesn't match your Node version.
|
|
9645
|
+
\u2022 Ensure you're on Node 20 LTS (run \`node -v\`).
|
|
9646
|
+
\u2022 Reinstall to fetch/rebuild the binary: \`npm rebuild better-sqlite3\` (or reinstall qaios).
|
|
9647
|
+
\u2022 Behind a proxy/firewall? The binary is fetched from GitHub releases \u2014 allow that, or install build tools so it can compile.
|
|
9648
|
+
Original error: ${raw}` : `Failed to initialise workflows.db: ${raw}`;
|
|
9627
9649
|
return {
|
|
9628
|
-
exitCode: ExitCode.INTERNAL,
|
|
9650
|
+
exitCode: err instanceof StorageError ? ExitCode.INTERNAL : ExitCode.TOOL_ERROR,
|
|
9629
9651
|
error: {
|
|
9630
|
-
code: err instanceof StorageError ? err.code : "qaios.init.db_failed",
|
|
9631
|
-
message
|
|
9652
|
+
code: isBindingError ? "qaios.init.sqlite_binding_missing" : err instanceof StorageError ? err.code : "qaios.init.db_failed",
|
|
9653
|
+
message
|
|
9632
9654
|
},
|
|
9633
9655
|
detection
|
|
9634
9656
|
};
|
|
9635
9657
|
}
|
|
9658
|
+
const filesWritten = [];
|
|
9636
9659
|
filesWritten.push(path12.relative(cwd, dbPath));
|
|
9660
|
+
const configPath = path12.join(qaiosDir, "config.yaml");
|
|
9661
|
+
writeFileSync(configPath, stringify(config), "utf-8");
|
|
9662
|
+
filesWritten.push(path12.relative(cwd, configPath));
|
|
9637
9663
|
const gitignorePath = path12.join(qaiosDir, ".gitignore");
|
|
9638
9664
|
writeFileSync(gitignorePath, QAIOS_GITIGNORE, "utf-8");
|
|
9639
9665
|
filesWritten.push(path12.relative(cwd, gitignorePath));
|
|
@@ -12015,13 +12041,19 @@ function formatDoctor(checks) {
|
|
|
12015
12041
|
return lines.join("\n");
|
|
12016
12042
|
}
|
|
12017
12043
|
var invokedAsScript = (() => {
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12044
|
+
const argv1 = process.argv[1];
|
|
12045
|
+
if (!argv1) return false;
|
|
12046
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
12047
|
+
const realOf = (p) => {
|
|
12048
|
+
try {
|
|
12049
|
+
return realpathSync(p);
|
|
12050
|
+
} catch {
|
|
12051
|
+
return path12.resolve(p);
|
|
12052
|
+
}
|
|
12053
|
+
};
|
|
12054
|
+
if (realOf(argv1) === realOf(thisFile)) return true;
|
|
12055
|
+
const invokedName = path12.basename(argv1).replace(/\.(js|mjs|cjs)$/, "");
|
|
12056
|
+
return invokedName === "qaios";
|
|
12025
12057
|
})();
|
|
12026
12058
|
if (invokedAsScript) {
|
|
12027
12059
|
installDeprecationWarningFilter();
|
|
@@ -18,6 +18,19 @@
|
|
|
18
18
|
// used unconditionally for stability — same input → same PNG → same SHA.
|
|
19
19
|
|
|
20
20
|
import { writeFileSync } from 'node:fs';
|
|
21
|
+
import { createRequire } from 'node:module';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { pathToFileURL } from 'node:url';
|
|
24
|
+
|
|
25
|
+
// Resolve `playwright` from the USER's project (process.cwd()), not from
|
|
26
|
+
// where this script lives in QAIOS's install dir. See axe-runner.mjs for
|
|
27
|
+
// the full rationale (a bare import resolves relative to this file, so a
|
|
28
|
+
// global QAIOS can't see the user's local playwright).
|
|
29
|
+
const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
|
|
30
|
+
const importFromProject = async (specifier) => {
|
|
31
|
+
const resolved = projectRequire.resolve(specifier);
|
|
32
|
+
return import(pathToFileURL(resolved).href);
|
|
33
|
+
};
|
|
21
34
|
|
|
22
35
|
const url = process.env.QAIOS_SCREENSHOT_URL;
|
|
23
36
|
const out = process.env.QAIOS_SCREENSHOT_OUTPUT;
|
|
@@ -37,7 +50,9 @@ const fullPage = process.env.QAIOS_SCREENSHOT_FULL_PAGE !== '0';
|
|
|
37
50
|
|
|
38
51
|
let chromium;
|
|
39
52
|
try {
|
|
40
|
-
|
|
53
|
+
const pw = await importFromProject('playwright');
|
|
54
|
+
chromium = pw.chromium ?? pw.default?.chromium;
|
|
55
|
+
if (!chromium) throw new Error('playwright resolved but has no chromium export');
|
|
41
56
|
} catch (err) {
|
|
42
57
|
process.stderr.write(
|
|
43
58
|
`capture-screenshot: failed to load 'playwright' from project — is it installed? ${err?.message ?? err}\n`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qatonic_innovations/qaios",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI QA engineer in your terminal — designs, writes, runs, heals, and explores tests for web UI and APIs with audit-grade traceability.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,10 +65,7 @@
|
|
|
65
65
|
"@types/pixelmatch": "^5.2.6",
|
|
66
66
|
"@types/pngjs": "^6.0.5",
|
|
67
67
|
"@types/react": "^18.3.28",
|
|
68
|
-
"ink-testing-library": "^4.0.0"
|
|
69
|
-
"@qaios/runtime": "0.0.0",
|
|
70
|
-
"@qaios/skills": "0.0.0",
|
|
71
|
-
"@qaios/shared": "0.0.0"
|
|
68
|
+
"ink-testing-library": "^4.0.0"
|
|
72
69
|
},
|
|
73
70
|
"scripts": {
|
|
74
71
|
"build": "tsup && node scripts/copy-templates.mjs && node scripts/copy-runtime-assets.mjs",
|