@qulib/core 0.8.2 → 0.10.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/README.md +38 -13
- package/bin/qulib.js +2 -3
- package/dist/__tests__/playwright-available.d.ts +32 -0
- package/dist/__tests__/playwright-available.d.ts.map +1 -0
- package/dist/__tests__/playwright-available.js +35 -0
- package/dist/adapters/ci-results-adapter.d.ts +67 -0
- package/dist/adapters/ci-results-adapter.d.ts.map +1 -0
- package/dist/adapters/ci-results-adapter.js +143 -0
- package/dist/adapters/cypress-e2e-adapter.d.ts.map +1 -1
- package/dist/adapters/cypress-e2e-adapter.js +25 -2
- package/dist/adapters/playwright-adapter.d.ts.map +1 -1
- package/dist/adapters/playwright-adapter.js +25 -2
- package/dist/adapters/pr-metadata-adapter.d.ts +75 -0
- package/dist/adapters/pr-metadata-adapter.d.ts.map +1 -0
- package/dist/adapters/pr-metadata-adapter.js +146 -0
- package/dist/adapters/validate-specs.d.ts +55 -0
- package/dist/adapters/validate-specs.d.ts.map +1 -0
- package/dist/adapters/validate-specs.js +67 -0
- package/dist/baseline/baseline.d.ts +54 -0
- package/dist/baseline/baseline.d.ts.map +1 -0
- package/dist/baseline/baseline.js +252 -0
- package/dist/baseline/baseline.schema.d.ts +233 -0
- package/dist/baseline/baseline.schema.d.ts.map +1 -0
- package/dist/baseline/baseline.schema.js +59 -0
- package/dist/cli/analyze-diff-run.d.ts +77 -0
- package/dist/cli/analyze-diff-run.d.ts.map +1 -0
- package/dist/cli/analyze-diff-run.js +266 -0
- package/dist/cli/baseline-run.d.ts +55 -0
- package/dist/cli/baseline-run.d.ts.map +1 -0
- package/dist/cli/baseline-run.js +259 -0
- package/dist/cli/confidence-run.d.ts +16 -0
- package/dist/cli/confidence-run.d.ts.map +1 -0
- package/dist/cli/confidence-run.js +162 -0
- package/dist/cli/index.d.ts +11 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +84 -4
- package/dist/cli/scaffold-run.d.ts +86 -0
- package/dist/cli/scaffold-run.d.ts.map +1 -0
- package/dist/cli/scaffold-run.js +232 -0
- package/dist/cli/score-automation-run.d.ts +25 -0
- package/dist/cli/score-automation-run.d.ts.map +1 -0
- package/dist/cli/score-automation-run.js +127 -0
- package/dist/examples/notquality-dogfood/fixture.d.ts +166 -0
- package/dist/examples/notquality-dogfood/fixture.d.ts.map +1 -0
- package/dist/examples/notquality-dogfood/fixture.js +174 -0
- package/dist/examples/notquality-dogfood/run.d.ts +34 -0
- package/dist/examples/notquality-dogfood/run.d.ts.map +1 -0
- package/dist/examples/notquality-dogfood/run.js +139 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/recipes/a11y.d.ts +36 -0
- package/dist/recipes/a11y.d.ts.map +1 -0
- package/dist/recipes/a11y.js +118 -0
- package/dist/recipes/auth.d.ts +38 -0
- package/dist/recipes/auth.d.ts.map +1 -0
- package/dist/recipes/auth.js +156 -0
- package/dist/recipes/index.d.ts +26 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +41 -0
- package/dist/recipes/nav.d.ts +34 -0
- package/dist/recipes/nav.d.ts.map +1 -0
- package/dist/recipes/nav.js +128 -0
- package/dist/recipes/seed.d.ts +34 -0
- package/dist/recipes/seed.d.ts.map +1 -0
- package/dist/recipes/seed.js +87 -0
- package/dist/reporters/heatmap.d.ts +55 -0
- package/dist/reporters/heatmap.d.ts.map +1 -0
- package/dist/reporters/heatmap.js +146 -0
- package/dist/reporters/markdown-reporter.d.ts.map +1 -1
- package/dist/reporters/markdown-reporter.js +4 -1
- package/dist/scaffold-tests.d.ts +21 -0
- package/dist/scaffold-tests.d.ts.map +1 -1
- package/dist/scaffold-tests.js +12 -2
- package/dist/schemas/confidence.schema.d.ts +526 -0
- package/dist/schemas/confidence.schema.d.ts.map +1 -0
- package/dist/schemas/confidence.schema.js +161 -0
- package/dist/schemas/config.schema.d.ts.map +1 -1
- package/dist/schemas/config.schema.js +6 -1
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +3 -0
- package/dist/schemas/recipe.schema.d.ts +66 -0
- package/dist/schemas/recipe.schema.d.ts.map +1 -0
- package/dist/schemas/recipe.schema.js +45 -0
- package/dist/schemas/views.schema.d.ts +234 -0
- package/dist/schemas/views.schema.d.ts.map +1 -0
- package/dist/schemas/views.schema.js +82 -0
- package/dist/tools/scoring/confidence-from-qulib.d.ts +34 -0
- package/dist/tools/scoring/confidence-from-qulib.d.ts.map +1 -0
- package/dist/tools/scoring/confidence-from-qulib.js +206 -0
- package/dist/tools/scoring/confidence-views.d.ts +40 -0
- package/dist/tools/scoring/confidence-views.d.ts.map +1 -0
- package/dist/tools/scoring/confidence-views.js +163 -0
- package/dist/tools/scoring/confidence.d.ts +32 -0
- package/dist/tools/scoring/confidence.d.ts.map +1 -0
- package/dist/tools/scoring/confidence.js +180 -0
- package/dist/tools/scoring/levels.d.ts +15 -0
- package/dist/tools/scoring/levels.d.ts.map +1 -0
- package/dist/tools/scoring/levels.js +21 -0
- package/package.json +18 -8
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/** Defaults from the proven NQ-2 data-testid convention. */
|
|
2
|
+
const DEFAULTS = {
|
|
3
|
+
loginUrl: '/login',
|
|
4
|
+
usernameSelector: '[data-testid=login-email]',
|
|
5
|
+
passwordSelector: '[data-testid=login-password]',
|
|
6
|
+
submitSelector: '[data-testid=login-submit]',
|
|
7
|
+
successSelector: '[data-testid=dashboard-root]',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Generate NeutralScenarios for the auth recipe.
|
|
11
|
+
*
|
|
12
|
+
* Returns 3 scenarios:
|
|
13
|
+
* 1. Happy-path login → lands on dashboard (smoke gate)
|
|
14
|
+
* 2. Invalid-credentials → inline error message shown (negative gate)
|
|
15
|
+
* 3. Protected route redirects unauthenticated users to login (security gate)
|
|
16
|
+
*
|
|
17
|
+
* All selectors come from the proven NQ-2/CaseLoom patterns and are overridable
|
|
18
|
+
* via RecipeConfig, so the recipe works for any form-login app.
|
|
19
|
+
*/
|
|
20
|
+
export function buildAuthScenarios(config = {}) {
|
|
21
|
+
const loginUrl = config.loginUrl ?? DEFAULTS.loginUrl;
|
|
22
|
+
const usernameSelector = config.usernameSelector ?? DEFAULTS.usernameSelector;
|
|
23
|
+
const passwordSelector = config.passwordSelector ?? DEFAULTS.passwordSelector;
|
|
24
|
+
const submitSelector = config.submitSelector ?? DEFAULTS.submitSelector;
|
|
25
|
+
const successSelector = config.successSelector ?? DEFAULTS.successSelector;
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
id: 'recipe-auth-happy-path',
|
|
29
|
+
title: 'User can log in with valid credentials',
|
|
30
|
+
description: 'Submitting valid credentials navigates to the authenticated dashboard',
|
|
31
|
+
targetPath: loginUrl,
|
|
32
|
+
steps: [
|
|
33
|
+
{ action: 'navigate', target: loginUrl, description: 'Open the login page' },
|
|
34
|
+
{
|
|
35
|
+
action: 'type',
|
|
36
|
+
target: usernameSelector,
|
|
37
|
+
value: 'user@example.test',
|
|
38
|
+
description: 'Enter email address',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
action: 'type',
|
|
42
|
+
target: passwordSelector,
|
|
43
|
+
value: 'correct-horse',
|
|
44
|
+
description: 'Enter password',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
action: 'click',
|
|
48
|
+
target: submitSelector,
|
|
49
|
+
description: 'Submit the login form',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
action: 'assert-visible',
|
|
53
|
+
target: successSelector,
|
|
54
|
+
description: 'Dashboard or success indicator is shown',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
tags: ['auth', 'smoke', 'recipe-auth'],
|
|
58
|
+
recommendations: [
|
|
59
|
+
{
|
|
60
|
+
adapter: 'cypress-e2e',
|
|
61
|
+
reason: 'Proven NQ-2 / CaseLoom pattern — cy.get + type/click/should',
|
|
62
|
+
confidence: 'high',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
adapter: 'playwright',
|
|
66
|
+
reason: 'Proven NQ-2 pattern — page.locator + fill/click/expect',
|
|
67
|
+
confidence: 'high',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
sourceGapIds: ['recipe-auth'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'recipe-auth-invalid-credentials',
|
|
74
|
+
title: 'Invalid credentials show an inline error',
|
|
75
|
+
description: 'Submitting wrong credentials stays on the login page and shows an error message',
|
|
76
|
+
targetPath: loginUrl,
|
|
77
|
+
steps: [
|
|
78
|
+
{ action: 'navigate', target: loginUrl, description: 'Open the login page' },
|
|
79
|
+
{
|
|
80
|
+
action: 'type',
|
|
81
|
+
target: usernameSelector,
|
|
82
|
+
value: 'user@example.test',
|
|
83
|
+
description: 'Enter email address',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
action: 'type',
|
|
87
|
+
target: passwordSelector,
|
|
88
|
+
value: 'wrong-password',
|
|
89
|
+
description: 'Enter an incorrect password',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
action: 'click',
|
|
93
|
+
target: submitSelector,
|
|
94
|
+
description: 'Submit with wrong credentials',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
action: 'assert-text',
|
|
98
|
+
target: '[data-testid=login-error]',
|
|
99
|
+
value: 'Invalid',
|
|
100
|
+
description: 'Inline error message is shown',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
action: 'assert-visible',
|
|
104
|
+
target: submitSelector,
|
|
105
|
+
description: 'Still on the login page (submit button visible)',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
tags: ['auth', 'negative', 'recipe-auth'],
|
|
109
|
+
recommendations: [
|
|
110
|
+
{
|
|
111
|
+
adapter: 'cypress-e2e',
|
|
112
|
+
reason: 'Proven NQ-2 negative path — assert-text on error element',
|
|
113
|
+
confidence: 'high',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
adapter: 'playwright',
|
|
117
|
+
reason: 'Proven NQ-2 negative path — toContainText on error element',
|
|
118
|
+
confidence: 'high',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
sourceGapIds: ['recipe-auth'],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'recipe-auth-protected-redirect',
|
|
125
|
+
title: 'Unauthenticated access to a protected route redirects to login',
|
|
126
|
+
description: 'Visiting a protected route without a session redirects to the login page — authentication guard is wired',
|
|
127
|
+
targetPath: '/dashboard',
|
|
128
|
+
steps: [
|
|
129
|
+
{
|
|
130
|
+
action: 'navigate',
|
|
131
|
+
target: '/dashboard',
|
|
132
|
+
description: 'Navigate directly to a protected route',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
action: 'assert-visible',
|
|
136
|
+
target: submitSelector,
|
|
137
|
+
description: 'Login form submit button is visible — we were redirected to login',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
tags: ['auth', 'security', 'recipe-auth'],
|
|
141
|
+
recommendations: [
|
|
142
|
+
{
|
|
143
|
+
adapter: 'cypress-e2e',
|
|
144
|
+
reason: 'Guards a critical security property — protected routes must redirect',
|
|
145
|
+
confidence: 'medium',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
adapter: 'playwright',
|
|
149
|
+
reason: 'Guards a critical security property — protected routes must redirect',
|
|
150
|
+
confidence: 'medium',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
sourceGapIds: ['recipe-auth'],
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe toolshed — reusable NeutralScenario builders.
|
|
3
|
+
*
|
|
4
|
+
* Each recipe module exports a build* function that returns NeutralScenario[].
|
|
5
|
+
* Recipes are merged (additive) into whatever scenarios the adapter already has;
|
|
6
|
+
* they never replace existing scenarios.
|
|
7
|
+
*
|
|
8
|
+
* Available recipes:
|
|
9
|
+
* auth — login / logout / protected-route flows
|
|
10
|
+
* a11y — accessibility checks (landmark/heading/aria/title)
|
|
11
|
+
* nav — deep-link / browser-back / 404-handling
|
|
12
|
+
* seed — data-seeding and state-reset helpers
|
|
13
|
+
*/
|
|
14
|
+
import type { RecipeId, RecipeConfig } from '../schemas/recipe.schema.js';
|
|
15
|
+
import type { NeutralScenario } from '../schemas/gap-analysis.schema.js';
|
|
16
|
+
export { buildAuthScenarios } from './auth.js';
|
|
17
|
+
export { buildA11yScenarios } from './a11y.js';
|
|
18
|
+
export { buildNavScenarios } from './nav.js';
|
|
19
|
+
export { buildSeedScenarios } from './seed.js';
|
|
20
|
+
/**
|
|
21
|
+
* Expand a list of RecipeIds into their NeutralScenario arrays, applying
|
|
22
|
+
* optional per-recipe config. Returns an empty array when ids is empty or
|
|
23
|
+
* undefined — safe to call unconditionally.
|
|
24
|
+
*/
|
|
25
|
+
export declare function expandRecipes(ids: RecipeId[] | undefined, config?: RecipeConfig): NeutralScenario[];
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recipes/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAMzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,QAAQ,EAAE,GAAG,SAAS,EAC3B,MAAM,GAAE,YAAiB,GACxB,eAAe,EAAE,CA2BnB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildAuthScenarios } from './auth.js';
|
|
2
|
+
import { buildA11yScenarios } from './a11y.js';
|
|
3
|
+
import { buildNavScenarios } from './nav.js';
|
|
4
|
+
import { buildSeedScenarios } from './seed.js';
|
|
5
|
+
export { buildAuthScenarios } from './auth.js';
|
|
6
|
+
export { buildA11yScenarios } from './a11y.js';
|
|
7
|
+
export { buildNavScenarios } from './nav.js';
|
|
8
|
+
export { buildSeedScenarios } from './seed.js';
|
|
9
|
+
/**
|
|
10
|
+
* Expand a list of RecipeIds into their NeutralScenario arrays, applying
|
|
11
|
+
* optional per-recipe config. Returns an empty array when ids is empty or
|
|
12
|
+
* undefined — safe to call unconditionally.
|
|
13
|
+
*/
|
|
14
|
+
export function expandRecipes(ids, config = {}) {
|
|
15
|
+
if (!ids || ids.length === 0)
|
|
16
|
+
return [];
|
|
17
|
+
const scenarios = [];
|
|
18
|
+
for (const id of ids) {
|
|
19
|
+
switch (id) {
|
|
20
|
+
case 'auth':
|
|
21
|
+
scenarios.push(...buildAuthScenarios(config));
|
|
22
|
+
break;
|
|
23
|
+
case 'a11y':
|
|
24
|
+
scenarios.push(...buildA11yScenarios(config));
|
|
25
|
+
break;
|
|
26
|
+
case 'nav':
|
|
27
|
+
scenarios.push(...buildNavScenarios(config));
|
|
28
|
+
break;
|
|
29
|
+
case 'seed':
|
|
30
|
+
scenarios.push(...buildSeedScenarios(config));
|
|
31
|
+
break;
|
|
32
|
+
default: {
|
|
33
|
+
// TypeScript exhaustiveness: if a new RecipeId is added without a case
|
|
34
|
+
// this will cause a compile error.
|
|
35
|
+
const _exhaustive = id;
|
|
36
|
+
throw new Error(`Unknown recipe id: ${String(_exhaustive)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return scenarios;
|
|
41
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nav recipe — navigation, deep-linking, back/forward, 404 handling.
|
|
3
|
+
*
|
|
4
|
+
* Lifted from the PROVEN NQ-2 Playwright navigation suite and CaseLoom Cypress
|
|
5
|
+
* navigation specs. Real selector and navigation patterns, re-derived from
|
|
6
|
+
* first principles.
|
|
7
|
+
*
|
|
8
|
+
* NQ-2 Playwright nav reference patterns:
|
|
9
|
+
* - page.goto('/about')
|
|
10
|
+
* - expect(page).toHaveURL(/\/about/)
|
|
11
|
+
* - await page.goBack()
|
|
12
|
+
* - expect(page).toHaveURL('/')
|
|
13
|
+
* - page.goto('/nonexistent-route-that-404s')
|
|
14
|
+
* - expect(page.locator('[data-testid=not-found]')).toBeVisible() OR
|
|
15
|
+
* - expect(page.getByText('404')).toBeVisible()
|
|
16
|
+
*
|
|
17
|
+
* CaseLoom Cypress nav reference patterns:
|
|
18
|
+
* - cy.visit('/about')
|
|
19
|
+
* - cy.url().should('include', '/about')
|
|
20
|
+
* - cy.go('back')
|
|
21
|
+
* - cy.url().should('eq', Cypress.config('baseUrl') + '/')
|
|
22
|
+
*/
|
|
23
|
+
import type { NeutralScenario } from '../schemas/gap-analysis.schema.js';
|
|
24
|
+
import type { RecipeConfig } from '../schemas/recipe.schema.js';
|
|
25
|
+
/**
|
|
26
|
+
* Generate NeutralScenarios for the nav recipe.
|
|
27
|
+
*
|
|
28
|
+
* Returns 3 scenarios:
|
|
29
|
+
* 1. Deep-link routes — direct navigation lands on the correct page
|
|
30
|
+
* 2. Browser-back — back button returns to the previous route
|
|
31
|
+
* 3. 404 handling — unknown routes show a user-friendly not-found page
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildNavScenarios(config?: RecipeConfig): NeutralScenario[];
|
|
34
|
+
//# sourceMappingURL=nav.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/recipes/nav.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAIhE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,YAAiB,GAAG,eAAe,EAAE,CA2H9E"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const DEFAULT_ROUTES = ['/', '/about', '/404'];
|
|
2
|
+
/**
|
|
3
|
+
* Generate NeutralScenarios for the nav recipe.
|
|
4
|
+
*
|
|
5
|
+
* Returns 3 scenarios:
|
|
6
|
+
* 1. Deep-link routes — direct navigation lands on the correct page
|
|
7
|
+
* 2. Browser-back — back button returns to the previous route
|
|
8
|
+
* 3. 404 handling — unknown routes show a user-friendly not-found page
|
|
9
|
+
*/
|
|
10
|
+
export function buildNavScenarios(config = {}) {
|
|
11
|
+
const routes = config.navRoutes ?? DEFAULT_ROUTES;
|
|
12
|
+
// Pick up to 3 routes (root + 1 deep + 404 fallback); guard against an empty list.
|
|
13
|
+
const root = routes[0] ?? '/';
|
|
14
|
+
const deep = routes[1] ?? '/about';
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
id: 'recipe-nav-deep-link',
|
|
18
|
+
title: 'Direct navigation to a deep route lands on the correct page',
|
|
19
|
+
description: 'Visiting a non-root route directly (without clicking through) loads the right content — SPA routing and server-side routing both produce the expected page',
|
|
20
|
+
targetPath: deep,
|
|
21
|
+
steps: [
|
|
22
|
+
{
|
|
23
|
+
action: 'navigate',
|
|
24
|
+
target: deep,
|
|
25
|
+
description: `Navigate directly to ${deep}`,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
action: 'assert-visible',
|
|
29
|
+
target: 'main',
|
|
30
|
+
description: 'Main content region is visible — page loaded',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
action: 'assert-visible',
|
|
34
|
+
target: 'h1',
|
|
35
|
+
description: 'Page has a heading — correct content loaded, not an error page',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
tags: ['nav', 'smoke', 'recipe-nav'],
|
|
39
|
+
recommendations: [
|
|
40
|
+
{
|
|
41
|
+
adapter: 'playwright',
|
|
42
|
+
reason: 'Proven NQ-2 nav pattern — page.goto + toHaveURL + heading assertion',
|
|
43
|
+
confidence: 'high',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
adapter: 'cypress-e2e',
|
|
47
|
+
reason: 'Proven CaseLoom nav pattern — cy.visit + cy.url().should',
|
|
48
|
+
confidence: 'high',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
sourceGapIds: ['recipe-nav'],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'recipe-nav-browser-back',
|
|
55
|
+
title: 'Browser back button returns to the previous route',
|
|
56
|
+
description: 'After navigating from the root to a deep route, pressing browser-back returns the user to the root — history stack is correctly maintained',
|
|
57
|
+
targetPath: deep,
|
|
58
|
+
steps: [
|
|
59
|
+
{
|
|
60
|
+
action: 'navigate',
|
|
61
|
+
target: root,
|
|
62
|
+
description: `Start at the root (${root})`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
action: 'navigate',
|
|
66
|
+
target: deep,
|
|
67
|
+
description: `Navigate forward to ${deep}`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
action: 'assert-visible',
|
|
71
|
+
target: 'h1',
|
|
72
|
+
description: 'Deep-route page loaded successfully',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
action: 'wait',
|
|
76
|
+
value: '300',
|
|
77
|
+
description: 'Let the navigation history settle',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
tags: ['nav', 'history', 'recipe-nav'],
|
|
81
|
+
recommendations: [
|
|
82
|
+
{
|
|
83
|
+
adapter: 'playwright',
|
|
84
|
+
reason: 'page.goBack() proves history is intact — proven NQ-2 pattern',
|
|
85
|
+
confidence: 'medium',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
adapter: 'cypress-e2e',
|
|
89
|
+
reason: 'cy.go("back") — proven CaseLoom history pattern',
|
|
90
|
+
confidence: 'medium',
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
sourceGapIds: ['recipe-nav'],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'recipe-nav-404-handling',
|
|
97
|
+
title: 'Unknown routes show a user-friendly 404 page',
|
|
98
|
+
description: 'Navigating to a non-existent route renders a helpful not-found page rather than a blank screen or an unhandled JS error',
|
|
99
|
+
targetPath: '/this-route-definitely-does-not-exist-qulib-404-probe',
|
|
100
|
+
steps: [
|
|
101
|
+
{
|
|
102
|
+
action: 'navigate',
|
|
103
|
+
target: '/this-route-definitely-does-not-exist-qulib-404-probe',
|
|
104
|
+
description: 'Navigate to a route guaranteed to be absent',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
action: 'assert-visible',
|
|
108
|
+
target: 'h1',
|
|
109
|
+
description: 'A heading is visible — the app renders a page (not-found or otherwise) rather than a blank error',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
tags: ['nav', 'error-handling', 'recipe-nav'],
|
|
113
|
+
recommendations: [
|
|
114
|
+
{
|
|
115
|
+
adapter: 'playwright',
|
|
116
|
+
reason: 'page.getByText("404") / not-found selector — proven NQ-2 error-handling pattern',
|
|
117
|
+
confidence: 'medium',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
adapter: 'cypress-e2e',
|
|
121
|
+
reason: 'cy.get fallback chain on error/not-found markers',
|
|
122
|
+
confidence: 'medium',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
sourceGapIds: ['recipe-nav'],
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* seed recipe — data-seeding and state-reset helpers.
|
|
3
|
+
*
|
|
4
|
+
* Lifted from the PROVEN NQ-2 Playwright + CaseLoom Cypress test-data patterns.
|
|
5
|
+
* Re-derived from first principles — not copy-pasted.
|
|
6
|
+
*
|
|
7
|
+
* NQ-2 Playwright seed reference patterns:
|
|
8
|
+
* - await request.post('/api/test/reset', { data: {} })
|
|
9
|
+
* - expect(response.status()).toBe(200)
|
|
10
|
+
* - await request.post('/api/test/seed', { data: { scenario: 'default' } })
|
|
11
|
+
*
|
|
12
|
+
* CaseLoom Cypress seed reference patterns:
|
|
13
|
+
* - cy.request('POST', '/api/test/reset')
|
|
14
|
+
* - cy.request('POST', '/api/test/seed', { scenario: 'default' })
|
|
15
|
+
* - cy.request({ method: 'DELETE', url: '/api/test/all' }).its('status').should('be.oneOf', [200, 204])
|
|
16
|
+
*
|
|
17
|
+
* The seed recipe is primarily a Playwright/API recipe because Cypress request()
|
|
18
|
+
* handles it cleanly, but the NeutralScenario shape covers both adapters via
|
|
19
|
+
* the api-call action.
|
|
20
|
+
*/
|
|
21
|
+
import type { NeutralScenario } from '../schemas/gap-analysis.schema.js';
|
|
22
|
+
import type { RecipeConfig } from '../schemas/recipe.schema.js';
|
|
23
|
+
/**
|
|
24
|
+
* Generate NeutralScenarios for the seed recipe.
|
|
25
|
+
*
|
|
26
|
+
* Returns 2 scenarios:
|
|
27
|
+
* 1. State reset — POST to the reset endpoint returns 200/204 (clean slate)
|
|
28
|
+
* 2. Seed + verify — POST seed then verify the seeded UI state is visible
|
|
29
|
+
*
|
|
30
|
+
* These are rendered as api-call steps, which both adapters handle (Cypress via
|
|
31
|
+
* cy.request, Playwright via page.request.post).
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildSeedScenarios(config?: RecipeConfig): NeutralScenario[];
|
|
34
|
+
//# sourceMappingURL=seed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../src/recipes/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE,YAAiB,GAAG,eAAe,EAAE,CA6E/E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const DEFAULT_SEED_ENDPOINT = '/api/test/seed';
|
|
2
|
+
const DEFAULT_RESET_ENDPOINT = '/api/test/reset';
|
|
3
|
+
/**
|
|
4
|
+
* Generate NeutralScenarios for the seed recipe.
|
|
5
|
+
*
|
|
6
|
+
* Returns 2 scenarios:
|
|
7
|
+
* 1. State reset — POST to the reset endpoint returns 200/204 (clean slate)
|
|
8
|
+
* 2. Seed + verify — POST seed then verify the seeded UI state is visible
|
|
9
|
+
*
|
|
10
|
+
* These are rendered as api-call steps, which both adapters handle (Cypress via
|
|
11
|
+
* cy.request, Playwright via page.request.post).
|
|
12
|
+
*/
|
|
13
|
+
export function buildSeedScenarios(config = {}) {
|
|
14
|
+
const seedEndpoint = config.seedEndpoint ?? DEFAULT_SEED_ENDPOINT;
|
|
15
|
+
const resetEndpoint = DEFAULT_RESET_ENDPOINT;
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
id: 'recipe-seed-reset',
|
|
19
|
+
title: 'Test state reset endpoint returns a success status',
|
|
20
|
+
description: 'POSTing to the reset endpoint clears test data and returns 200 — the test teardown contract is honoured',
|
|
21
|
+
targetPath: resetEndpoint,
|
|
22
|
+
steps: [
|
|
23
|
+
{
|
|
24
|
+
action: 'api-call',
|
|
25
|
+
target: resetEndpoint,
|
|
26
|
+
description: 'POST to the reset endpoint — expect 200/204',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
tags: ['seed', 'setup', 'recipe-seed'],
|
|
30
|
+
recommendations: [
|
|
31
|
+
{
|
|
32
|
+
adapter: 'playwright',
|
|
33
|
+
reason: 'page.request.post / apiRequest — proven NQ-2 data-setup pattern',
|
|
34
|
+
confidence: 'high',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
adapter: 'cypress-e2e',
|
|
38
|
+
reason: 'cy.request("POST", url) — proven CaseLoom seed pattern',
|
|
39
|
+
confidence: 'high',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
adapter: 'api',
|
|
43
|
+
reason: 'Direct API seed — no browser required',
|
|
44
|
+
confidence: 'high',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
sourceGapIds: ['recipe-seed'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'recipe-seed-and-verify',
|
|
51
|
+
title: 'Seed endpoint populates data that is visible in the UI',
|
|
52
|
+
description: 'After seeding the default dataset, the UI reflects the seeded state — the test pipeline can establish known state before running assertions',
|
|
53
|
+
targetPath: '/',
|
|
54
|
+
steps: [
|
|
55
|
+
{
|
|
56
|
+
action: 'api-call',
|
|
57
|
+
target: seedEndpoint,
|
|
58
|
+
description: `POST to seed endpoint (${seedEndpoint}) to populate default test data`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
action: 'navigate',
|
|
62
|
+
target: '/',
|
|
63
|
+
description: 'Navigate to the app root to verify the seeded state is visible',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
action: 'assert-visible',
|
|
67
|
+
target: 'main',
|
|
68
|
+
description: 'Application loaded with seeded data',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
tags: ['seed', 'data-setup', 'recipe-seed'],
|
|
72
|
+
recommendations: [
|
|
73
|
+
{
|
|
74
|
+
adapter: 'playwright',
|
|
75
|
+
reason: 'Proven NQ-2 seed + navigate pattern — set state before assertions',
|
|
76
|
+
confidence: 'medium',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
adapter: 'cypress-e2e',
|
|
80
|
+
reason: 'cy.request seed then cy.visit — proven CaseLoom setup pattern',
|
|
81
|
+
confidence: 'medium',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
sourceGapIds: ['recipe-seed'],
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-page coverage heatmap for Markdown reports.
|
|
3
|
+
*
|
|
4
|
+
* buildPageHeatmap() is a pure function — no I/O, no side-effects.
|
|
5
|
+
* It derives a rows × dimensions matrix from GapAnalysis.gaps and sorts
|
|
6
|
+
* rows worst-first (most critical coverage problems bubble to the top).
|
|
7
|
+
*
|
|
8
|
+
* Dimensions map to the GapSchema.category enum values so the heatmap
|
|
9
|
+
* always stays in sync with what the scanner can produce.
|
|
10
|
+
*/
|
|
11
|
+
import type { Gap } from '../schemas/gap-analysis.schema.js';
|
|
12
|
+
/** The ordered set of gap categories that appear as heatmap columns. */
|
|
13
|
+
export declare const HEATMAP_DIMENSIONS: readonly ["untested-route", "a11y", "console-error", "broken-link", "coverage", "untested-api-endpoint", "auth-surface"];
|
|
14
|
+
export type HeatmapDimension = (typeof HEATMAP_DIMENSIONS)[number];
|
|
15
|
+
/** Display labels for each dimension column header. */
|
|
16
|
+
export declare const DIMENSION_LABELS: Record<HeatmapDimension, string>;
|
|
17
|
+
/** One cell in the heatmap: the worst severity for that page × dimension. */
|
|
18
|
+
export type HeatmapCell = {
|
|
19
|
+
/** Worst Gap severity found, or null when no gap exists. */
|
|
20
|
+
worstSeverity: Gap['severity'] | null;
|
|
21
|
+
/** Glyph to render in Markdown. */
|
|
22
|
+
glyph: string;
|
|
23
|
+
/** Count of gaps contributing to this cell. */
|
|
24
|
+
count: number;
|
|
25
|
+
};
|
|
26
|
+
/** One row in the heatmap: a page path and its per-dimension cells. */
|
|
27
|
+
export type HeatmapRow = {
|
|
28
|
+
path: string;
|
|
29
|
+
/** Map from dimension to cell. Guaranteed to contain every HEATMAP_DIMENSION key. */
|
|
30
|
+
cells: Record<HeatmapDimension, HeatmapCell>;
|
|
31
|
+
/** Sum of severity scores across all cells — used for worst-first sort. */
|
|
32
|
+
worstScore: number;
|
|
33
|
+
};
|
|
34
|
+
/** The full heatmap structure returned by buildPageHeatmap(). */
|
|
35
|
+
export type PageHeatmap = {
|
|
36
|
+
/** Rows sorted worst-first (highest total severity score first). */
|
|
37
|
+
rows: HeatmapRow[];
|
|
38
|
+
/** Ordered dimension labels for use as column headers. */
|
|
39
|
+
dimensions: typeof HEATMAP_DIMENSIONS;
|
|
40
|
+
/** Total number of gaps that fed into the heatmap. */
|
|
41
|
+
totalGaps: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Build a per-page coverage heatmap from a flat list of gaps.
|
|
45
|
+
*
|
|
46
|
+
* @param gaps The gaps array from GapAnalysis.
|
|
47
|
+
* @returns A PageHeatmap with rows sorted worst-first.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildPageHeatmap(gaps: Gap[]): PageHeatmap;
|
|
50
|
+
/**
|
|
51
|
+
* Render a PageHeatmap as a Markdown section string.
|
|
52
|
+
* Returns an empty string when there are no rows (nothing scanned).
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderHeatmapSection(heatmap: PageHeatmap): string;
|
|
55
|
+
//# sourceMappingURL=heatmap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heatmap.d.ts","sourceRoot":"","sources":["../../src/reporters/heatmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mCAAmC,CAAC;AAM7D,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,0HAQoB,CAAC;AAEpD,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,uDAAuD;AACvD,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAQ7D,CAAC;AAuBF,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG;IACxB,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IACtC,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG;IACxB,oEAAoE;IACpE,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,0DAA0D;IAC1D,UAAU,EAAE,OAAO,kBAAkB,CAAC;IACtC,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,WAAW,CAuDzD;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAmCjE"}
|