@qatonic_innovations/qaios 0.1.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/LICENSE +21 -0
- package/dist/QAIOS.md.template +70 -0
- package/dist/a11y/scripts/axe-runner.mjs +82 -0
- package/dist/healing/scripts/capture-page.mjs +56 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +12035 -0
- package/dist/migrations/0001_init.sql +182 -0
- package/dist/migrations/0002_baselines_unique_and_workflow_lease.sql +50 -0
- package/dist/visual/scripts/capture-screenshot.mjs +69 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QATONIC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# QAIOS Project Memory
|
|
2
|
+
|
|
3
|
+
> Auto-generated by `qaios init`. Hand-edit this file as your project grows —
|
|
4
|
+
> QAIOS reads it before every skill invocation and uses it to bias test design,
|
|
5
|
+
> writing, and classification toward your conventions.
|
|
6
|
+
|
|
7
|
+
## Project Overview
|
|
8
|
+
|
|
9
|
+
{{projectName}}
|
|
10
|
+
|
|
11
|
+
<!-- Replace with a few sentences about what this app does, who uses it, and
|
|
12
|
+
what "working correctly" means at a high level. -->
|
|
13
|
+
|
|
14
|
+
## Stack
|
|
15
|
+
|
|
16
|
+
- **Frontend:** {{stack}}
|
|
17
|
+
- **Backend:** <add your backend>
|
|
18
|
+
- **Database:** <add your database>
|
|
19
|
+
- **Test framework:** {{testFramework}}
|
|
20
|
+
|
|
21
|
+
## Test Conventions
|
|
22
|
+
|
|
23
|
+
- **Test directory:** `{{testDir}}/`
|
|
24
|
+
- **Spec naming:** `<feature>.spec.ts`
|
|
25
|
+
- **Locator preference:** `data-testid` > `role` > `text`
|
|
26
|
+
- **Avoid:** CSS class selectors (volatile), absolute XPath, locators based on
|
|
27
|
+
generated styles or transient classes
|
|
28
|
+
- **Test-id attribute:** Playwright's `getByTestId()` resolves `data-testid` by
|
|
29
|
+
default. If your app uses a different attribute (e.g. `data-test`, `data-qa`),
|
|
30
|
+
set `testing.testIdAttribute` in `.qaios/config.yaml` so generated specs find
|
|
31
|
+
your elements — otherwise every `getByTestId` locator times out.
|
|
32
|
+
|
|
33
|
+
## Risk Hotspots
|
|
34
|
+
|
|
35
|
+
<!-- Modules / flows that warrant heavy testing. QAIOS escalates risk on
|
|
36
|
+
anything touching these areas. Examples below; replace with yours. -->
|
|
37
|
+
|
|
38
|
+
- Authentication flow (PII, lockout logic)
|
|
39
|
+
- Payment processing (idempotency, webhooks)
|
|
40
|
+
- File uploads (size limits, MIME validation)
|
|
41
|
+
|
|
42
|
+
## Domain Oracles
|
|
43
|
+
|
|
44
|
+
<!-- What "correct" means for your specific app. Concrete, observable
|
|
45
|
+
post-conditions QAIOS can assert against. -->
|
|
46
|
+
|
|
47
|
+
- A successful login: redirect to `/dashboard`, session cookie set, user email
|
|
48
|
+
visible in nav
|
|
49
|
+
- A successful payment: order in `confirmed` state, email sent, audit trail
|
|
50
|
+
entry written
|
|
51
|
+
|
|
52
|
+
## Test Data
|
|
53
|
+
|
|
54
|
+
- **Seed script:** `<path/to/seed-script>`
|
|
55
|
+
- **Test users:** see `<fixtures path>`
|
|
56
|
+
- **PII rule:** never use real emails — always `*@qaios.test`
|
|
57
|
+
|
|
58
|
+
## CI Context
|
|
59
|
+
|
|
60
|
+
- **Runs on:** <e.g. GitHub Actions, ubuntu-latest>
|
|
61
|
+
- **Env file:** `<path/to/staging.env>`
|
|
62
|
+
- **Quarantine policy:** flaky tests auto-skip after 3 consecutive failures,
|
|
63
|
+
re-evaluated weekly
|
|
64
|
+
|
|
65
|
+
## Known Flakiness
|
|
66
|
+
|
|
67
|
+
<!-- Document specific known-flaky tests or routes. QAIOS uses this to bias
|
|
68
|
+
classification away from labelling these as real failures. -->
|
|
69
|
+
|
|
70
|
+
- _(none yet — add here as patterns emerge)_
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// axe-runner.mjs — deterministic accessibility scan for `qaios a11y`.
|
|
3
|
+
//
|
|
4
|
+
// Runs in the USER's project (cwd) so it can dynamic-import their
|
|
5
|
+
// installed `@axe-core/playwright` + `playwright` packages — QAIOS
|
|
6
|
+
// bundles neither. Reads inputs from env vars:
|
|
7
|
+
// QAIOS_AXE_URL — target URL (required)
|
|
8
|
+
// QAIOS_AXE_OUTPUT — output path for the findings JSON (required)
|
|
9
|
+
// QAIOS_AXE_TIMEOUT_MS — navigation timeout (default 30_000)
|
|
10
|
+
// QAIOS_AXE_TAGS — comma-separated axe tags (default wcag2a,wcag2aa)
|
|
11
|
+
// QAIOS_AXE_WAIT_UNTIL — load|domcontentloaded|networkidle (default 'domcontentloaded')
|
|
12
|
+
//
|
|
13
|
+
// Emits axe-core's findings (violations + incomplete, each tagged with a
|
|
14
|
+
// `kind`: 'violation' | 'incomplete' | 'best_practice') as the
|
|
15
|
+
// deterministic source of truth. Exit codes mirror capture-page.mjs:
|
|
16
|
+
// 2 = missing env vars
|
|
17
|
+
// 3 = @axe-core/playwright or playwright not installed
|
|
18
|
+
// 4 = navigation / scan error
|
|
19
|
+
// 0 = success (even when violations were found)
|
|
20
|
+
|
|
21
|
+
import { writeFileSync } from 'node:fs';
|
|
22
|
+
|
|
23
|
+
const url = process.env.QAIOS_AXE_URL;
|
|
24
|
+
const out = process.env.QAIOS_AXE_OUTPUT;
|
|
25
|
+
const timeoutMs = Number.parseInt(process.env.QAIOS_AXE_TIMEOUT_MS ?? '30000', 10);
|
|
26
|
+
const waitUntil = process.env.QAIOS_AXE_WAIT_UNTIL ?? 'domcontentloaded';
|
|
27
|
+
const tags = (process.env.QAIOS_AXE_TAGS ?? 'wcag2a,wcag2aa')
|
|
28
|
+
.split(',')
|
|
29
|
+
.map((t) => t.trim())
|
|
30
|
+
.filter((t) => t.length > 0);
|
|
31
|
+
|
|
32
|
+
if (!url || !out) {
|
|
33
|
+
process.stderr.write('axe-runner: QAIOS_AXE_URL and QAIOS_AXE_OUTPUT env vars are required\n');
|
|
34
|
+
process.exit(2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let chromium, AxeBuilder;
|
|
38
|
+
try {
|
|
39
|
+
({ chromium } = await import('playwright'));
|
|
40
|
+
({ default: AxeBuilder } = await import('@axe-core/playwright'));
|
|
41
|
+
} catch (err) {
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
`axe-runner: failed to load 'playwright' and '@axe-core/playwright' from the project — ` +
|
|
44
|
+
`install them with \`npm i -D @axe-core/playwright\` (which pulls playwright). ` +
|
|
45
|
+
`${err?.message ?? err}\n`,
|
|
46
|
+
);
|
|
47
|
+
process.exit(3);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const browser = await chromium.launch({ headless: true });
|
|
51
|
+
try {
|
|
52
|
+
const context = await browser.newContext();
|
|
53
|
+
const page = await context.newPage();
|
|
54
|
+
await page.goto(url, { timeout: timeoutMs, waitUntil });
|
|
55
|
+
|
|
56
|
+
const builder = new AxeBuilder({ page });
|
|
57
|
+
if (tags.length > 0) builder.withTags(tags);
|
|
58
|
+
const results = await builder.analyze();
|
|
59
|
+
|
|
60
|
+
// axe buckets its findings (https://github.com/dequelabs/axe-core docs):
|
|
61
|
+
// violations — undeniable WCAG defects (must fix).
|
|
62
|
+
// incomplete — needs manual review (axe couldn't be certain, e.g.
|
|
63
|
+
// contrast behind a background image).
|
|
64
|
+
// A rule is a "best practice" (recommendation, not a WCAG break) when
|
|
65
|
+
// its tags include 'best-practice'. We tag each finding with `kind` so
|
|
66
|
+
// the QAIOS side can gate on real violations only and label the rest.
|
|
67
|
+
const isBestPractice = (v) => Array.isArray(v.tags) && v.tags.includes('best-practice');
|
|
68
|
+
const tag = (arr, fallbackKind) =>
|
|
69
|
+
(arr ?? []).map((v) => ({ ...v, kind: isBestPractice(v) ? 'best_practice' : fallbackKind }));
|
|
70
|
+
|
|
71
|
+
const findings = [
|
|
72
|
+
...tag(results.violations, 'violation'),
|
|
73
|
+
...tag(results.incomplete, 'incomplete'),
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
writeFileSync(out, JSON.stringify({ url, findings }), 'utf-8');
|
|
77
|
+
} catch (err) {
|
|
78
|
+
process.stderr.write(`axe-runner: ${err?.message ?? err}\n`);
|
|
79
|
+
process.exit(4);
|
|
80
|
+
} finally {
|
|
81
|
+
await browser.close();
|
|
82
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// capture-page.mjs — Playwright headless page capture for qaios fix.
|
|
3
|
+
//
|
|
4
|
+
// Runs in the USER's project (cwd) so it can dynamic-import their
|
|
5
|
+
// installed `playwright` package. Reads inputs from env vars:
|
|
6
|
+
// QAIOS_CAPTURE_URL — target URL (required)
|
|
7
|
+
// QAIOS_CAPTURE_OUTPUT — output path for {url, html, accessibilityTree} JSON (required)
|
|
8
|
+
// QAIOS_CAPTURE_TIMEOUT_MS — navigation timeout (default 10_000)
|
|
9
|
+
// QAIOS_CAPTURE_WAIT_UNTIL — load|domcontentloaded|networkidle (default 'domcontentloaded')
|
|
10
|
+
//
|
|
11
|
+
// Emits the full HTML; truncation to 50KB happens on the QAIOS side
|
|
12
|
+
// via truncateHtmlSafe so the script stays decoupled from the budget.
|
|
13
|
+
|
|
14
|
+
import { writeFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const url = process.env.QAIOS_CAPTURE_URL;
|
|
17
|
+
const out = process.env.QAIOS_CAPTURE_OUTPUT;
|
|
18
|
+
const timeoutMs = Number.parseInt(process.env.QAIOS_CAPTURE_TIMEOUT_MS ?? '10000', 10);
|
|
19
|
+
const waitUntil = process.env.QAIOS_CAPTURE_WAIT_UNTIL ?? 'domcontentloaded';
|
|
20
|
+
|
|
21
|
+
if (!url || !out) {
|
|
22
|
+
process.stderr.write(
|
|
23
|
+
'capture-page: QAIOS_CAPTURE_URL and QAIOS_CAPTURE_OUTPUT env vars are required\n',
|
|
24
|
+
);
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let chromium;
|
|
29
|
+
try {
|
|
30
|
+
({ chromium } = await import('playwright'));
|
|
31
|
+
} catch (err) {
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
`capture-page: failed to load 'playwright' from project — is it installed? ${err?.message ?? err}\n`,
|
|
34
|
+
);
|
|
35
|
+
process.exit(3);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const browser = await chromium.launch({ headless: true });
|
|
39
|
+
try {
|
|
40
|
+
const context = await browser.newContext();
|
|
41
|
+
const page = await context.newPage();
|
|
42
|
+
await page.goto(url, { timeout: timeoutMs, waitUntil });
|
|
43
|
+
const html = await page.content();
|
|
44
|
+
let accessibilityTree = null;
|
|
45
|
+
try {
|
|
46
|
+
accessibilityTree = await page.accessibility.snapshot();
|
|
47
|
+
} catch {
|
|
48
|
+
/* accessibility snapshot is best-effort */
|
|
49
|
+
}
|
|
50
|
+
writeFileSync(out, JSON.stringify({ url, html, accessibilityTree }), 'utf-8');
|
|
51
|
+
} catch (err) {
|
|
52
|
+
process.stderr.write(`capture-page: ${err?.message ?? err}\n`);
|
|
53
|
+
process.exit(4);
|
|
54
|
+
} finally {
|
|
55
|
+
await browser.close();
|
|
56
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
type CheckStatus = 'ok' | 'warn' | 'fail' | 'skip';
|
|
4
|
+
interface DoctorCheck {
|
|
5
|
+
name: string;
|
|
6
|
+
status: CheckStatus;
|
|
7
|
+
detail: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build the Commander program. Exported so tests can introspect the parser
|
|
12
|
+
* without spawning a subprocess.
|
|
13
|
+
*/
|
|
14
|
+
declare function buildProgram(): Command;
|
|
15
|
+
declare function formatDoctor(checks: DoctorCheck[]): string;
|
|
16
|
+
|
|
17
|
+
export { buildProgram, formatDoctor };
|