@qulib/core 0.6.0 → 0.8.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/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts +7 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts.map +1 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.js +7 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts +10 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts.map +1 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.js +9 -0
- package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts +9 -0
- package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts.map +1 -0
- package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.js +10 -0
- package/dist/adapters/api-adapter.d.ts +26 -0
- package/dist/adapters/api-adapter.d.ts.map +1 -1
- package/dist/adapters/api-adapter.js +156 -2
- package/dist/adapters/cypress-e2e-adapter.d.ts.map +1 -1
- package/dist/adapters/cypress-e2e-adapter.js +63 -2
- package/dist/adapters/playwright-adapter.d.ts.map +1 -1
- package/dist/adapters/playwright-adapter.js +71 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/scaffold-tests.d.ts +34 -0
- package/dist/scaffold-tests.d.ts.map +1 -0
- package/dist/scaffold-tests.js +113 -0
- package/dist/schemas/automation-maturity.schema.d.ts +8 -8
- package/dist/schemas/automation-maturity.schema.d.ts.map +1 -1
- package/dist/schemas/automation-maturity.schema.js +1 -0
- package/dist/schemas/gap-analysis.schema.d.ts +8 -8
- package/dist/schemas/gap-analysis.schema.js +1 -1
- package/dist/schemas/public-surface.schema.d.ts +5 -5
- package/dist/schemas/repo-analysis.schema.d.ts +7 -7
- package/dist/tools/repo/api-surface.d.ts +59 -0
- package/dist/tools/repo/api-surface.d.ts.map +1 -0
- package/dist/tools/repo/api-surface.js +414 -0
- package/dist/tools/scoring/api-coverage.d.ts +74 -0
- package/dist/tools/scoring/api-coverage.d.ts.map +1 -0
- package/dist/tools/scoring/api-coverage.js +158 -0
- package/dist/tools/scoring/automation-maturity.d.ts +11 -1
- package/dist/tools/scoring/automation-maturity.d.ts.map +1 -1
- package/dist/tools/scoring/automation-maturity.js +43 -9
- package/package.json +4 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/__tests__/fixtures/api-fixture-repo/app/api/orders/route.ts"],"names":[],"mappings":"AAGA,wBAAsB,MAAM,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE;;;GAIpD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Fixture: Next.js App Router API route for orders (DELETE only — high severity untested)
|
|
2
|
+
// This file is a static analysis fixture only — not compiled.
|
|
3
|
+
export async function DELETE(request) {
|
|
4
|
+
const url = new URL(request.url);
|
|
5
|
+
const id = url.searchParams.get('id');
|
|
6
|
+
return { deleted: true, id };
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/__tests__/fixtures/api-fixture-repo/app/api/users/route.ts"],"names":[],"mappings":"AAGA,wBAAsB,GAAG;;GAExB;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE;;;GAGnE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Fixture: Next.js App Router API route for users
|
|
2
|
+
// This file is a static analysis fixture only — not compiled.
|
|
3
|
+
export async function GET() {
|
|
4
|
+
return { users: [] };
|
|
5
|
+
}
|
|
6
|
+
export async function POST(request) {
|
|
7
|
+
const body = await request.json();
|
|
8
|
+
return { created: true, user: body };
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../../../../src/__tests__/fixtures/api-fixture-repo/pages/api/health.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,OAAO,CAC7B,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACxB,GAAG,EAAE;IAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;CAAE,QAOtF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Fixture: Next.js Pages API route
|
|
2
|
+
// This file is a static analysis fixture only — not compiled.
|
|
3
|
+
export default function handler(req, res) {
|
|
4
|
+
if (req.method === 'GET') {
|
|
5
|
+
res.status(200).json({ status: 'ok' });
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
res.status(405).end();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import type { TestAdapter } from './adapter.interface.js';
|
|
2
2
|
import type { NeutralScenario, GeneratedTest } from '../schemas/gap-analysis.schema.js';
|
|
3
|
+
import type { ApiSurface } from '../tools/repo/api-surface.js';
|
|
4
|
+
/**
|
|
5
|
+
* TestAdapter implementation for API testing via supertest.
|
|
6
|
+
*
|
|
7
|
+
* `render` / `renderAll`: convert gap-analysis NeutralScenarios that carry
|
|
8
|
+
* `api-call` steps into supertest specs. Used by the standard adapter pipeline.
|
|
9
|
+
*
|
|
10
|
+
* `scaffoldApiTests`: separate entry point for the repo-first API toolshed flow.
|
|
11
|
+
* Accepts discovered endpoints (ApiSurface) and generates a ready-to-run
|
|
12
|
+
* supertest test file — NOT URL-based.
|
|
13
|
+
*/
|
|
3
14
|
export declare class ApiAdapter implements TestAdapter {
|
|
4
15
|
readonly adapterType = "api";
|
|
5
16
|
render(scenario: NeutralScenario): GeneratedTest;
|
|
6
17
|
renderAll(scenarios: NeutralScenario[]): GeneratedTest[];
|
|
18
|
+
/**
|
|
19
|
+
* Generate a supertest-based test file from discovered API endpoints.
|
|
20
|
+
* This is the repo-first entry point — does NOT require a running URL.
|
|
21
|
+
*
|
|
22
|
+
* Endpoints are grouped into a single test file. Each endpoint gets one
|
|
23
|
+
* `it` block that:
|
|
24
|
+
* - Makes the correct HTTP method call
|
|
25
|
+
* - Asserts status < 500 (smoke-level assertion, safely runnable against a live app)
|
|
26
|
+
* - POST/PUT/PATCH endpoints include a TODO for request body
|
|
27
|
+
*
|
|
28
|
+
* The file is NOT associated with a NeutralScenario; it uses a fixed scenarioId.
|
|
29
|
+
*/
|
|
30
|
+
scaffoldApiTests(apiSurface: ApiSurface, options?: {
|
|
31
|
+
appImportPath?: string;
|
|
32
|
+
}): GeneratedTest;
|
|
7
33
|
}
|
|
8
34
|
//# sourceMappingURL=api-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/api-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"api-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/api-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,KAAK,EAAsB,UAAU,EAAE,MAAM,8BAA8B,CAAC;AASnF;;;;;;;;;GASG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,WAAW,SAAS;IAE7B,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IAuDhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;IAIxD;;;;;;;;;;;OAWG;IACH,gBAAgB,CACd,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAO,GACvC,aAAa;CAoDjB"}
|
|
@@ -1,9 +1,163 @@
|
|
|
1
|
+
function slugify(title) {
|
|
2
|
+
return title
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
5
|
+
.replace(/^-|-$/g, '');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* TestAdapter implementation for API testing via supertest.
|
|
9
|
+
*
|
|
10
|
+
* `render` / `renderAll`: convert gap-analysis NeutralScenarios that carry
|
|
11
|
+
* `api-call` steps into supertest specs. Used by the standard adapter pipeline.
|
|
12
|
+
*
|
|
13
|
+
* `scaffoldApiTests`: separate entry point for the repo-first API toolshed flow.
|
|
14
|
+
* Accepts discovered endpoints (ApiSurface) and generates a ready-to-run
|
|
15
|
+
* supertest test file — NOT URL-based.
|
|
16
|
+
*/
|
|
1
17
|
export class ApiAdapter {
|
|
2
18
|
adapterType = 'api';
|
|
3
19
|
render(scenario) {
|
|
4
|
-
|
|
20
|
+
const slug = slugify(scenario.title);
|
|
21
|
+
const filename = `${slug}.api.test.ts`;
|
|
22
|
+
const stepLines = scenario.steps
|
|
23
|
+
.map((step) => {
|
|
24
|
+
if (step.action === 'api-call') {
|
|
25
|
+
const path = step.target ?? step.value ?? '/';
|
|
26
|
+
return [
|
|
27
|
+
` // ${step.description}`,
|
|
28
|
+
` const res = await request(app).get(${JSON.stringify(path)});`,
|
|
29
|
+
` expect(res.status).toBe(200);`,
|
|
30
|
+
].join('\n');
|
|
31
|
+
}
|
|
32
|
+
if (step.action === 'navigate') {
|
|
33
|
+
const path = step.target ?? step.value ?? '/';
|
|
34
|
+
return [
|
|
35
|
+
` // ${step.description}`,
|
|
36
|
+
` const res = await request(app).get(${JSON.stringify(path)});`,
|
|
37
|
+
` expect(res.status).toBeLessThan(500);`,
|
|
38
|
+
].join('\n');
|
|
39
|
+
}
|
|
40
|
+
return ` // TODO (${step.action}): ${step.description}`;
|
|
41
|
+
})
|
|
42
|
+
.join('\n');
|
|
43
|
+
const code = [
|
|
44
|
+
`// ${scenario.description}`,
|
|
45
|
+
`// qulib-generated — scenario: ${scenario.id}`,
|
|
46
|
+
``,
|
|
47
|
+
`import request from 'supertest';`,
|
|
48
|
+
`import { describe, it, expect } from 'vitest';`,
|
|
49
|
+
``,
|
|
50
|
+
`// TODO: import or create your Express/Fastify/Hono app here`,
|
|
51
|
+
`// import { app } from '../src/app.js';`,
|
|
52
|
+
`declare const app: unknown;`,
|
|
53
|
+
``,
|
|
54
|
+
`describe(${JSON.stringify(scenario.title)}, () => {`,
|
|
55
|
+
` it(${JSON.stringify(scenario.description)}, async () => {`,
|
|
56
|
+
stepLines || ` // no api-call steps — add assertions for: ${scenario.targetPath}`,
|
|
57
|
+
` });`,
|
|
58
|
+
`});`,
|
|
59
|
+
``,
|
|
60
|
+
].join('\n');
|
|
61
|
+
return {
|
|
62
|
+
scenarioId: scenario.id,
|
|
63
|
+
adapter: 'api',
|
|
64
|
+
filename,
|
|
65
|
+
code,
|
|
66
|
+
source: 'template',
|
|
67
|
+
outputPath: `tests/api/${filename}`,
|
|
68
|
+
};
|
|
5
69
|
}
|
|
6
70
|
renderAll(scenarios) {
|
|
7
|
-
|
|
71
|
+
return scenarios.map((s) => this.render(s));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate a supertest-based test file from discovered API endpoints.
|
|
75
|
+
* This is the repo-first entry point — does NOT require a running URL.
|
|
76
|
+
*
|
|
77
|
+
* Endpoints are grouped into a single test file. Each endpoint gets one
|
|
78
|
+
* `it` block that:
|
|
79
|
+
* - Makes the correct HTTP method call
|
|
80
|
+
* - Asserts status < 500 (smoke-level assertion, safely runnable against a live app)
|
|
81
|
+
* - POST/PUT/PATCH endpoints include a TODO for request body
|
|
82
|
+
*
|
|
83
|
+
* The file is NOT associated with a NeutralScenario; it uses a fixed scenarioId.
|
|
84
|
+
*/
|
|
85
|
+
scaffoldApiTests(apiSurface, options = {}) {
|
|
86
|
+
const appImport = options.appImportPath ?? '../src/app.js';
|
|
87
|
+
const endpoints = apiSurface.endpoints;
|
|
88
|
+
if (endpoints.length === 0) {
|
|
89
|
+
const code = [
|
|
90
|
+
`// qulib-generated API scaffold — no endpoints discovered`,
|
|
91
|
+
`// qulib-generated — repo: ${apiSurface.repoPath}`,
|
|
92
|
+
``,
|
|
93
|
+
`// No API endpoints were discovered in this repository.`,
|
|
94
|
+
`// If your app has REST endpoints, ensure they are declared in a supported`,
|
|
95
|
+
`// framework (Next.js route.ts, Express, Fastify, NestJS) or an OpenAPI spec.`,
|
|
96
|
+
``,
|
|
97
|
+
].join('\n');
|
|
98
|
+
return {
|
|
99
|
+
scenarioId: 'qulib-api-scaffold',
|
|
100
|
+
adapter: 'api',
|
|
101
|
+
filename: 'api-scaffold.test.ts',
|
|
102
|
+
code,
|
|
103
|
+
source: 'template',
|
|
104
|
+
outputPath: 'tests/api/api-scaffold.test.ts',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const itBlocks = endpoints.map((ep) => renderEndpointTest(ep)).join('\n\n');
|
|
108
|
+
const code = [
|
|
109
|
+
`// qulib-generated API scaffold — ${endpoints.length} endpoint(s) discovered`,
|
|
110
|
+
`// qulib-generated — repo: ${apiSurface.repoPath}`,
|
|
111
|
+
`// Discovery tier breakdown: ${describeDiscoveryTiers(endpoints)}`,
|
|
112
|
+
``,
|
|
113
|
+
`import request from 'supertest';`,
|
|
114
|
+
`import { describe, it, expect, beforeAll, afterAll } from 'vitest';`,
|
|
115
|
+
``,
|
|
116
|
+
`// TODO: replace with your actual app export`,
|
|
117
|
+
`import { app } from ${JSON.stringify(appImport)};`,
|
|
118
|
+
``,
|
|
119
|
+
`describe('API surface smoke tests (qulib-generated)', () => {`,
|
|
120
|
+
itBlocks,
|
|
121
|
+
`});`,
|
|
122
|
+
``,
|
|
123
|
+
].join('\n');
|
|
124
|
+
return {
|
|
125
|
+
scenarioId: 'qulib-api-scaffold',
|
|
126
|
+
adapter: 'api',
|
|
127
|
+
filename: 'api-scaffold.test.ts',
|
|
128
|
+
code,
|
|
129
|
+
source: 'template',
|
|
130
|
+
outputPath: 'tests/api/api-scaffold.test.ts',
|
|
131
|
+
};
|
|
8
132
|
}
|
|
9
133
|
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Internal helpers
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
function renderEndpointTest(ep) {
|
|
138
|
+
const method = ep.method === 'unknown' ? 'GET' : ep.method;
|
|
139
|
+
const methodLower = method.toLowerCase();
|
|
140
|
+
const hasBody = method === 'POST' || method === 'PUT' || method === 'PATCH';
|
|
141
|
+
const sourceLine = ` // Source: ${ep.sourceFile} (${ep.sourceTier}, confidence: ${ep.confidence})`;
|
|
142
|
+
const itTitle = `${method} ${ep.path}`;
|
|
143
|
+
const requestLine = hasBody
|
|
144
|
+
? ` const res = await request(app).${methodLower}(${JSON.stringify(ep.path)});\n // TODO: add request body — e.g. .send({ ... })`
|
|
145
|
+
: ` const res = await request(app).${methodLower}(${JSON.stringify(ep.path)});`;
|
|
146
|
+
const summaryLine = ep.summary ? ` // ${ep.summary}\n` : '';
|
|
147
|
+
return [
|
|
148
|
+
summaryLine + sourceLine,
|
|
149
|
+
` it(${JSON.stringify(itTitle)}, async () => {`,
|
|
150
|
+
requestLine,
|
|
151
|
+
` expect(res.status).toBeLessThan(500);`,
|
|
152
|
+
` });`,
|
|
153
|
+
].join('\n');
|
|
154
|
+
}
|
|
155
|
+
function describeDiscoveryTiers(endpoints) {
|
|
156
|
+
const counts = { openapi: 0, framework: 0, heuristic: 0 };
|
|
157
|
+
for (const ep of endpoints)
|
|
158
|
+
counts[ep.sourceTier]++;
|
|
159
|
+
return Object.entries(counts)
|
|
160
|
+
.filter(([, v]) => v > 0)
|
|
161
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
162
|
+
.join(', ');
|
|
163
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cypress-e2e-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/cypress-e2e-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"cypress-e2e-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/cypress-e2e-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAY,MAAM,mCAAmC,CAAC;AA2ClG,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,WAAW,iBAAiB;IAErC,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IA4BhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;CAGzD"}
|
|
@@ -1,9 +1,70 @@
|
|
|
1
|
+
function slugify(title) {
|
|
2
|
+
return title
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
5
|
+
.replace(/^-|-$/g, '');
|
|
6
|
+
}
|
|
7
|
+
function renderStep(step) {
|
|
8
|
+
const t = step.target != null ? JSON.stringify(step.target) : null;
|
|
9
|
+
const v = step.value != null ? JSON.stringify(step.value) : null;
|
|
10
|
+
switch (step.action) {
|
|
11
|
+
case 'navigate':
|
|
12
|
+
return ` cy.visit(${JSON.stringify(step.target ?? step.value ?? '/')});`;
|
|
13
|
+
case 'click':
|
|
14
|
+
return t ? ` cy.get(${t}).click();` : ` // click: ${step.description}`;
|
|
15
|
+
case 'type':
|
|
16
|
+
return t && v ? ` cy.get(${t}).type(${v});` : ` // type: ${step.description}`;
|
|
17
|
+
case 'assert-visible':
|
|
18
|
+
return t ? ` cy.get(${t}).should('be.visible');` : ` cy.get('body').should('be.visible');`;
|
|
19
|
+
case 'assert-hidden':
|
|
20
|
+
return t ? ` cy.get(${t}).should('not.be.visible');` : ` // assert-hidden: ${step.description}`;
|
|
21
|
+
case 'assert-text':
|
|
22
|
+
if (t && v)
|
|
23
|
+
return ` cy.get(${t}).should('contain.text', ${v});`;
|
|
24
|
+
if (t)
|
|
25
|
+
return ` cy.get(${t}).should('not.be.empty');`;
|
|
26
|
+
return ` // assert-text: ${step.description}`;
|
|
27
|
+
case 'assert-disabled':
|
|
28
|
+
return t ? ` cy.get(${t}).should('be.disabled');` : ` // assert-disabled: ${step.description}`;
|
|
29
|
+
case 'assert-count':
|
|
30
|
+
return t
|
|
31
|
+
? ` cy.get(${t}).should('have.length.gte', ${parseInt(step.value ?? '1', 10)});`
|
|
32
|
+
: ` // assert-count: ${step.description}`;
|
|
33
|
+
case 'wait':
|
|
34
|
+
return ` cy.wait(${parseInt(step.value ?? '1000', 10)});`;
|
|
35
|
+
case 'api-call':
|
|
36
|
+
return ` cy.request(${JSON.stringify(step.target ?? step.value ?? '/')}).its('status').should('eq', 200);`;
|
|
37
|
+
default:
|
|
38
|
+
return ` // TODO: ${step.description}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
1
41
|
export class CypressE2EAdapter {
|
|
2
42
|
adapterType = 'cypress-e2e';
|
|
3
43
|
render(scenario) {
|
|
4
|
-
|
|
44
|
+
const slug = slugify(scenario.title);
|
|
45
|
+
const filename = `${slug}.cy.ts`;
|
|
46
|
+
const stepLines = scenario.steps.map(renderStep).join('\n');
|
|
47
|
+
const code = [
|
|
48
|
+
`// ${scenario.description}`,
|
|
49
|
+
`// qulib-generated — scenario: ${scenario.id}`,
|
|
50
|
+
``,
|
|
51
|
+
`describe(${JSON.stringify(scenario.title)}, () => {`,
|
|
52
|
+
` it(${JSON.stringify(scenario.description)}, () => {`,
|
|
53
|
+
stepLines || ` // no steps — add assertions for: ${scenario.targetPath}`,
|
|
54
|
+
` });`,
|
|
55
|
+
`});`,
|
|
56
|
+
``,
|
|
57
|
+
].join('\n');
|
|
58
|
+
return {
|
|
59
|
+
scenarioId: scenario.id,
|
|
60
|
+
adapter: 'cypress-e2e',
|
|
61
|
+
filename,
|
|
62
|
+
code,
|
|
63
|
+
source: 'template',
|
|
64
|
+
outputPath: `cypress/e2e/${filename}`,
|
|
65
|
+
};
|
|
5
66
|
}
|
|
6
67
|
renderAll(scenarios) {
|
|
7
|
-
|
|
68
|
+
return scenarios.map((s) => this.render(s));
|
|
8
69
|
}
|
|
9
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/playwright-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/playwright-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAY,MAAM,mCAAmC,CAAC;AAiDlG,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,WAAW,gBAAgB;IAEpC,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IA8BhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;CAGzD"}
|
|
@@ -1,9 +1,78 @@
|
|
|
1
|
+
function slugify(title) {
|
|
2
|
+
return title
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
5
|
+
.replace(/^-|-$/g, '');
|
|
6
|
+
}
|
|
7
|
+
function renderStep(step) {
|
|
8
|
+
const t = step.target != null ? JSON.stringify(step.target) : null;
|
|
9
|
+
const v = step.value != null ? JSON.stringify(step.value) : null;
|
|
10
|
+
switch (step.action) {
|
|
11
|
+
case 'navigate':
|
|
12
|
+
return ` await page.goto(${JSON.stringify(step.target ?? step.value ?? '/')});`;
|
|
13
|
+
case 'click':
|
|
14
|
+
return t ? ` await page.locator(${t}).click();` : ` // click: ${step.description}`;
|
|
15
|
+
case 'type':
|
|
16
|
+
return t && v ? ` await page.locator(${t}).fill(${v});` : ` // type: ${step.description}`;
|
|
17
|
+
case 'assert-visible':
|
|
18
|
+
return t
|
|
19
|
+
? ` await expect(page.locator(${t})).toBeVisible();`
|
|
20
|
+
: ` await expect(page.locator('body')).toBeVisible();`;
|
|
21
|
+
case 'assert-hidden':
|
|
22
|
+
return t
|
|
23
|
+
? ` await expect(page.locator(${t})).toBeHidden();`
|
|
24
|
+
: ` // assert-hidden: ${step.description}`;
|
|
25
|
+
case 'assert-text':
|
|
26
|
+
if (t && v)
|
|
27
|
+
return ` await expect(page.locator(${t})).toContainText(${v});`;
|
|
28
|
+
if (t)
|
|
29
|
+
return ` await expect(page.locator(${t})).not.toBeEmpty();`;
|
|
30
|
+
return ` // assert-text: ${step.description}`;
|
|
31
|
+
case 'assert-disabled':
|
|
32
|
+
return t
|
|
33
|
+
? ` await expect(page.locator(${t})).toBeDisabled();`
|
|
34
|
+
: ` // assert-disabled: ${step.description}`;
|
|
35
|
+
case 'assert-count':
|
|
36
|
+
return t
|
|
37
|
+
? ` expect(await page.locator(${t}).count()).toBeGreaterThanOrEqual(${parseInt(step.value ?? '1', 10)});`
|
|
38
|
+
: ` // assert-count: ${step.description}`;
|
|
39
|
+
case 'wait':
|
|
40
|
+
return ` await page.waitForTimeout(${parseInt(step.value ?? '1000', 10)});`;
|
|
41
|
+
case 'api-call':
|
|
42
|
+
return ` expect((await page.request.get(${JSON.stringify(step.target ?? step.value ?? '/')})).status()).toBe(200);`;
|
|
43
|
+
default:
|
|
44
|
+
return ` // TODO: ${step.description}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
1
47
|
export class PlaywrightAdapter {
|
|
2
48
|
adapterType = 'playwright';
|
|
3
49
|
render(scenario) {
|
|
4
|
-
|
|
50
|
+
const slug = slugify(scenario.title);
|
|
51
|
+
const filename = `${slug}.spec.ts`;
|
|
52
|
+
const stepLines = scenario.steps.map(renderStep).join('\n');
|
|
53
|
+
const code = [
|
|
54
|
+
`// ${scenario.description}`,
|
|
55
|
+
`// qulib-generated — scenario: ${scenario.id}`,
|
|
56
|
+
``,
|
|
57
|
+
`import { test, expect } from '@playwright/test';`,
|
|
58
|
+
``,
|
|
59
|
+
`test.describe(${JSON.stringify(scenario.title)}, () => {`,
|
|
60
|
+
` test(${JSON.stringify(scenario.description)}, async ({ page }) => {`,
|
|
61
|
+
stepLines || ` // no steps — add assertions for: ${scenario.targetPath}`,
|
|
62
|
+
` });`,
|
|
63
|
+
`});`,
|
|
64
|
+
``,
|
|
65
|
+
].join('\n');
|
|
66
|
+
return {
|
|
67
|
+
scenarioId: scenario.id,
|
|
68
|
+
adapter: 'playwright',
|
|
69
|
+
filename,
|
|
70
|
+
code,
|
|
71
|
+
source: 'template',
|
|
72
|
+
outputPath: `tests/${filename}`,
|
|
73
|
+
};
|
|
5
74
|
}
|
|
6
75
|
renderAll(scenarios) {
|
|
7
|
-
|
|
76
|
+
return scenarios.map((s) => this.render(s));
|
|
8
77
|
}
|
|
9
78
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,13 @@ export type { StorageStateInvalidReason, StorageStateValidationResult, } from '.
|
|
|
6
6
|
export { exploreAuth } from './tools/auth/explore.js';
|
|
7
7
|
export { addUserProvider, removeUserProvider, listUserProviders } from './tools/auth/custom-providers.js';
|
|
8
8
|
export { scanRepo } from './tools/repo/scan.js';
|
|
9
|
+
export { discoverApiSurface, discoverApiSurfaceWithRepo } from './tools/repo/api-surface.js';
|
|
10
|
+
export type { ApiSurface, DiscoveredEndpoint, DiscoverApiSurfaceOptions } from './tools/repo/api-surface.js';
|
|
9
11
|
export { computeAutomationMaturity } from './tools/scoring/automation-maturity.js';
|
|
12
|
+
export { computeApiCoverage } from './tools/scoring/api-coverage.js';
|
|
13
|
+
export type { ApiCoverageResult, ApiEndpointCoverage } from './tools/scoring/api-coverage.js';
|
|
14
|
+
export { scaffoldTests } from './scaffold-tests.js';
|
|
15
|
+
export type { ScaffoldOptions, ScaffoldResult, ProjectConfig } from './scaffold-tests.js';
|
|
10
16
|
export { createProvider } from './llm/provider-registry.js';
|
|
11
17
|
export { resolveMaxOutputTokensPerLlmCall } from './schemas/config.schema.js';
|
|
12
18
|
export { resolveScanStateBaseDir, resolveReportDir } from './harness/state-manager.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAC7G,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,10 @@ export { detectAuth, validateStorageState, evaluateStorageStateValidity, preflig
|
|
|
4
4
|
export { exploreAuth } from './tools/auth/explore.js';
|
|
5
5
|
export { addUserProvider, removeUserProvider, listUserProviders } from './tools/auth/custom-providers.js';
|
|
6
6
|
export { scanRepo } from './tools/repo/scan.js';
|
|
7
|
+
export { discoverApiSurface, discoverApiSurfaceWithRepo } from './tools/repo/api-surface.js';
|
|
7
8
|
export { computeAutomationMaturity } from './tools/scoring/automation-maturity.js';
|
|
9
|
+
export { computeApiCoverage } from './tools/scoring/api-coverage.js';
|
|
10
|
+
export { scaffoldTests } from './scaffold-tests.js';
|
|
8
11
|
export { createProvider } from './llm/provider-registry.js';
|
|
9
12
|
export { resolveMaxOutputTokensPerLlmCall } from './schemas/config.schema.js';
|
|
10
13
|
export { resolveScanStateBaseDir, resolveReportDir } from './harness/state-manager.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { NeutralScenario, GeneratedTest } from './schemas/gap-analysis.schema.js';
|
|
2
|
+
import type { AdapterType } from './schemas/config.schema.js';
|
|
3
|
+
import type { AnalyzeProgressSink } from './harness/progress-log.js';
|
|
4
|
+
import type { TelemetrySink } from './telemetry/telemetry.interface.js';
|
|
5
|
+
export interface ScaffoldOptions {
|
|
6
|
+
framework?: Extract<AdapterType, 'cypress-e2e' | 'playwright'>;
|
|
7
|
+
maxPagesToScan?: number;
|
|
8
|
+
scenarios?: NeutralScenario[];
|
|
9
|
+
progressLog?: AnalyzeProgressSink;
|
|
10
|
+
telemetry?: TelemetrySink;
|
|
11
|
+
}
|
|
12
|
+
export interface ProjectConfig {
|
|
13
|
+
configFile: {
|
|
14
|
+
filename: string;
|
|
15
|
+
code: string;
|
|
16
|
+
};
|
|
17
|
+
packageJson: {
|
|
18
|
+
devDependencies: Record<string, string>;
|
|
19
|
+
scripts: Record<string, string>;
|
|
20
|
+
};
|
|
21
|
+
supportFiles: Array<{
|
|
22
|
+
filename: string;
|
|
23
|
+
code: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export interface ScaffoldResult {
|
|
27
|
+
url: string;
|
|
28
|
+
framework: Extract<AdapterType, 'cypress-e2e' | 'playwright'>;
|
|
29
|
+
generatedTests: GeneratedTest[];
|
|
30
|
+
scenarios: NeutralScenario[];
|
|
31
|
+
projectConfig: ProjectConfig;
|
|
32
|
+
}
|
|
33
|
+
export declare function scaffoldTests(url: string, options?: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
34
|
+
//# sourceMappingURL=scaffold-tests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold-tests.d.ts","sourceRoot":"","sources":["../src/scaffold-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACvF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,aAAa,GAAG,YAAY,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,WAAW,EAAE;QAAE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IAC1F,YAAY,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE,aAAa,GAAG,YAAY,CAAC,CAAC;IAC9D,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,aAAa,EAAE,aAAa,CAAC;CAC9B;AA8ED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAyCzB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { analyzeApp } from './analyze.js';
|
|
2
|
+
import { createAdapter } from './adapters/adapter-factory.js';
|
|
3
|
+
function buildCypressProjectConfig(url) {
|
|
4
|
+
return {
|
|
5
|
+
configFile: {
|
|
6
|
+
filename: 'cypress.config.ts',
|
|
7
|
+
code: [
|
|
8
|
+
`import { defineConfig } from 'cypress';`,
|
|
9
|
+
``,
|
|
10
|
+
`export default defineConfig({`,
|
|
11
|
+
` e2e: {`,
|
|
12
|
+
` baseUrl: ${JSON.stringify(url)},`,
|
|
13
|
+
` viewportWidth: 1280,`,
|
|
14
|
+
` viewportHeight: 720,`,
|
|
15
|
+
` defaultCommandTimeout: 10000,`,
|
|
16
|
+
` pageLoadTimeout: 30000,`,
|
|
17
|
+
` video: false,`,
|
|
18
|
+
` screenshotOnRunFailure: true,`,
|
|
19
|
+
` screenshotsFolder: 'results/screenshots',`,
|
|
20
|
+
` specPattern: 'cypress/e2e/**/*.cy.ts',`,
|
|
21
|
+
` supportFile: 'cypress/support/e2e.ts',`,
|
|
22
|
+
` },`,
|
|
23
|
+
`});`,
|
|
24
|
+
``,
|
|
25
|
+
].join('\n'),
|
|
26
|
+
},
|
|
27
|
+
packageJson: {
|
|
28
|
+
devDependencies: {
|
|
29
|
+
cypress: '^13.0.0',
|
|
30
|
+
typescript: '^5.4.0',
|
|
31
|
+
},
|
|
32
|
+
scripts: {
|
|
33
|
+
test: 'cypress run',
|
|
34
|
+
'test:headed': 'cypress open',
|
|
35
|
+
'test:ci': 'cypress run --reporter json --reporter-options output=results/cypress-results.json',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
supportFiles: [
|
|
39
|
+
{
|
|
40
|
+
filename: 'cypress/support/e2e.ts',
|
|
41
|
+
code: [
|
|
42
|
+
`Cypress.on('uncaught:exception', () => false);`,
|
|
43
|
+
``,
|
|
44
|
+
].join('\n'),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function buildPlaywrightProjectConfig(url) {
|
|
50
|
+
return {
|
|
51
|
+
configFile: {
|
|
52
|
+
filename: 'playwright.config.ts',
|
|
53
|
+
code: [
|
|
54
|
+
`import { defineConfig, devices } from '@playwright/test';`,
|
|
55
|
+
``,
|
|
56
|
+
`export default defineConfig({`,
|
|
57
|
+
` use: { baseURL: ${JSON.stringify(url)} },`,
|
|
58
|
+
` projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],`,
|
|
59
|
+
`});`,
|
|
60
|
+
``,
|
|
61
|
+
].join('\n'),
|
|
62
|
+
},
|
|
63
|
+
packageJson: {
|
|
64
|
+
devDependencies: {
|
|
65
|
+
'@playwright/test': '^1.44.0',
|
|
66
|
+
typescript: '^5.4.0',
|
|
67
|
+
},
|
|
68
|
+
scripts: {
|
|
69
|
+
test: 'playwright test',
|
|
70
|
+
'test:headed': 'playwright test --headed',
|
|
71
|
+
'test:ci': 'playwright test --reporter=json',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
supportFiles: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export async function scaffoldTests(url, options = {}) {
|
|
78
|
+
const framework = options.framework ?? 'cypress-e2e';
|
|
79
|
+
let scenarios;
|
|
80
|
+
if (options.scenarios && options.scenarios.length > 0) {
|
|
81
|
+
scenarios = options.scenarios;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const result = await analyzeApp({
|
|
85
|
+
url,
|
|
86
|
+
config: {
|
|
87
|
+
maxPagesToScan: options.maxPagesToScan ?? 10,
|
|
88
|
+
maxDepth: 3,
|
|
89
|
+
minPagesForConfidence: 3,
|
|
90
|
+
timeoutMs: 30000,
|
|
91
|
+
retryCount: 0,
|
|
92
|
+
llmTokenBudget: 4096,
|
|
93
|
+
testGenerationLimit: 10,
|
|
94
|
+
enableLlmScenarios: true,
|
|
95
|
+
readOnlyMode: true,
|
|
96
|
+
requireHumanReview: false,
|
|
97
|
+
failOnConsoleError: false,
|
|
98
|
+
explorer: 'playwright',
|
|
99
|
+
defaultAdapter: framework,
|
|
100
|
+
adapters: [framework],
|
|
101
|
+
},
|
|
102
|
+
progressLog: options.progressLog,
|
|
103
|
+
telemetry: options.telemetry,
|
|
104
|
+
});
|
|
105
|
+
scenarios = result.gapAnalysis.scenarios;
|
|
106
|
+
}
|
|
107
|
+
const adapter = createAdapter(framework);
|
|
108
|
+
const generatedTests = adapter.renderAll(scenarios);
|
|
109
|
+
const projectConfig = framework === 'cypress-e2e'
|
|
110
|
+
? buildCypressProjectConfig(url)
|
|
111
|
+
: buildPlaywrightProjectConfig(url);
|
|
112
|
+
return { url, framework, generatedTests, scenarios, projectConfig };
|
|
113
|
+
}
|