qa360 2.0.11 → 2.0.13
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/commands/ai.js +26 -14
- package/dist/commands/ask.d.ts +75 -23
- package/dist/commands/ask.js +413 -265
- package/dist/commands/crawl.d.ts +24 -0
- package/dist/commands/crawl.js +121 -0
- package/dist/commands/history.js +38 -3
- package/dist/commands/init.d.ts +89 -95
- package/dist/commands/init.js +282 -200
- package/dist/commands/run.d.ts +1 -0
- package/dist/core/adapters/playwright-ui.d.ts +45 -7
- package/dist/core/adapters/playwright-ui.js +365 -59
- package/dist/core/assertions/engine.d.ts +51 -0
- package/dist/core/assertions/engine.js +530 -0
- package/dist/core/assertions/index.d.ts +11 -0
- package/dist/core/assertions/index.js +11 -0
- package/dist/core/assertions/types.d.ts +121 -0
- package/dist/core/assertions/types.js +37 -0
- package/dist/core/crawler/index.d.ts +57 -0
- package/dist/core/crawler/index.js +281 -0
- package/dist/core/crawler/journey-generator.d.ts +49 -0
- package/dist/core/crawler/journey-generator.js +412 -0
- package/dist/core/crawler/page-analyzer.d.ts +88 -0
- package/dist/core/crawler/page-analyzer.js +709 -0
- package/dist/core/crawler/selector-generator.d.ts +34 -0
- package/dist/core/crawler/selector-generator.js +240 -0
- package/dist/core/crawler/types.d.ts +353 -0
- package/dist/core/crawler/types.js +6 -0
- package/dist/core/generation/crawler-pack-generator.d.ts +44 -0
- package/dist/core/generation/crawler-pack-generator.js +231 -0
- package/dist/core/generation/index.d.ts +2 -0
- package/dist/core/generation/index.js +2 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/types/pack-v1.d.ts +90 -0
- package/dist/index.js +6 -2
- package/examples/accessibility.yml +39 -16
- package/examples/api-basic.yml +19 -14
- package/examples/complete.yml +134 -42
- package/examples/fullstack.yml +66 -31
- package/examples/security.yml +47 -15
- package/examples/ui-basic.yml +16 -12
- package/package.json +3 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Crawl Command
|
|
3
|
+
*
|
|
4
|
+
* Crawls a website and generates a complete pack.yml with E2E tests
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* Create crawl command
|
|
9
|
+
*/
|
|
10
|
+
export declare function createCrawlCommand(): Command;
|
|
11
|
+
/**
|
|
12
|
+
* Crawl command handler
|
|
13
|
+
*/
|
|
14
|
+
export declare function crawlCommand(url: string, options?: {
|
|
15
|
+
output?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
maxDepth?: string;
|
|
18
|
+
maxPages?: string;
|
|
19
|
+
headed?: boolean;
|
|
20
|
+
a11y?: boolean;
|
|
21
|
+
exclude?: string[];
|
|
22
|
+
quick?: boolean;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export default createCrawlCommand;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Crawl Command
|
|
3
|
+
*
|
|
4
|
+
* Crawls a website and generates a complete pack.yml with E2E tests
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { generatePackFromCrawl, quickCrawl } from '../core/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create crawl command
|
|
12
|
+
*/
|
|
13
|
+
export function createCrawlCommand() {
|
|
14
|
+
const cmd = new Command('crawl')
|
|
15
|
+
.description('Crawl website and generate E2E test pack')
|
|
16
|
+
.argument('<url>', 'Website URL to crawl')
|
|
17
|
+
.option('-o, --output <file>', 'Output pack file path', 'pack.yml')
|
|
18
|
+
.option('--name <name>', 'Pack name')
|
|
19
|
+
.option('--max-depth <number>', 'Maximum crawl depth', '2')
|
|
20
|
+
.option('--max-pages <number>', 'Maximum pages to crawl', '20')
|
|
21
|
+
.option('--headed', 'Run crawler in headed mode (visible browser)')
|
|
22
|
+
.option('--a11y', 'Include accessibility tests')
|
|
23
|
+
.option('--exclude <patterns...>', 'URL patterns to exclude')
|
|
24
|
+
.option('--quick', 'Quick crawl (depth=1, max-pages=10)')
|
|
25
|
+
.action(async (url, options) => {
|
|
26
|
+
await crawlCommand(url, options);
|
|
27
|
+
});
|
|
28
|
+
return cmd;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Crawl command handler
|
|
32
|
+
*/
|
|
33
|
+
export async function crawlCommand(url, options = {}) {
|
|
34
|
+
const spinner = ora('Initializing crawler...').start();
|
|
35
|
+
try {
|
|
36
|
+
// Validate URL
|
|
37
|
+
new URL(url);
|
|
38
|
+
if (options.quick) {
|
|
39
|
+
spinner.info('Running quick crawl...');
|
|
40
|
+
const result = await quickCrawl(url, options.output);
|
|
41
|
+
if (result.success) {
|
|
42
|
+
spinner.succeed(`Generated pack: ${result.packPath}`);
|
|
43
|
+
console.log(chalk.green('\n✅ Crawl complete!'));
|
|
44
|
+
console.log(chalk.gray(` Pack: ${result.packPath}`));
|
|
45
|
+
console.log(chalk.gray('\nNext steps:'));
|
|
46
|
+
console.log(chalk.gray(' 1. Review the generated pack.yml'));
|
|
47
|
+
console.log(chalk.gray(' 2. Run tests: qa360 run ' + (options.output || 'pack.yml')));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
spinner.fail('Crawl failed');
|
|
51
|
+
console.error(chalk.red(result.error));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Full crawl
|
|
55
|
+
spinner.info('Crawling website...');
|
|
56
|
+
const crawlOptions = {
|
|
57
|
+
baseUrl: url,
|
|
58
|
+
output: options.output,
|
|
59
|
+
packName: options.name,
|
|
60
|
+
includeA11y: options.a11y,
|
|
61
|
+
crawl: {
|
|
62
|
+
maxDepth: options.quick ? 1 : parseInt(options.maxDepth || '2', 10),
|
|
63
|
+
maxPages: options.quick ? 10 : parseInt(options.maxPages || '20', 10),
|
|
64
|
+
headless: !options.headed,
|
|
65
|
+
excludePatterns: options.exclude,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const result = await generatePackFromCrawl(crawlOptions);
|
|
69
|
+
if (result.success) {
|
|
70
|
+
spinner.succeed(`Generated pack: ${result.packPath}`);
|
|
71
|
+
// Show crawl summary
|
|
72
|
+
console.log(chalk.bold('\n📊 Crawl Summary'));
|
|
73
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
74
|
+
if (result.result) {
|
|
75
|
+
const { siteMap, userJourneys, forms } = result.result;
|
|
76
|
+
console.log(`\n Pages discovered: ${chalk.green(siteMap.metadata.pagesCrawled)}`);
|
|
77
|
+
console.log(` Forms found: ${chalk.green(forms.length)}`);
|
|
78
|
+
console.log(` User journeys: ${chalk.green(userJourneys.length)}`);
|
|
79
|
+
if (siteMap.metadata.avgLoadTime) {
|
|
80
|
+
console.log(` Avg page load: ${chalk.gray(siteMap.metadata.avgLoadTime + 'ms')}`);
|
|
81
|
+
}
|
|
82
|
+
// Show journeys
|
|
83
|
+
if (userJourneys.length > 0) {
|
|
84
|
+
console.log(chalk.bold('\n Generated Journeys:'));
|
|
85
|
+
for (const journey of userJourneys.slice(0, 5)) {
|
|
86
|
+
console.log(` • ${chalk.cyan(journey.name)} (${journey.steps.length} steps)`);
|
|
87
|
+
}
|
|
88
|
+
if (userJourneys.length > 5) {
|
|
89
|
+
console.log(chalk.gray(` ... and ${userJourneys.length - 5} more`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Show page types
|
|
93
|
+
if (Object.keys(siteMap.pageTypeDistribution).length > 0) {
|
|
94
|
+
console.log(chalk.bold('\n Page Types:'));
|
|
95
|
+
for (const [type, count] of Object.entries(siteMap.pageTypeDistribution)) {
|
|
96
|
+
console.log(` • ${type}: ${count}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
console.log(chalk.bold('\n✅ Crawl complete!'));
|
|
101
|
+
console.log(chalk.gray(`\nNext steps:`));
|
|
102
|
+
console.log(chalk.gray(` 1. Review the generated pack: ${chalk.cyan(options.output || 'pack.yml')}`));
|
|
103
|
+
console.log(chalk.gray(` 2. Run tests: ${chalk.cyan('qa360 run ' + (options.output || 'pack.yml'))}`));
|
|
104
|
+
console.log(chalk.gray(` 3. Run in headed mode: ${chalk.cyan('qa360 run --headed ' + (options.output || 'pack.yml'))}`));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
spinner.fail('Crawl failed');
|
|
108
|
+
console.error(chalk.red(result.error || 'Unknown error'));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
spinner.fail('Crawl error');
|
|
113
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
114
|
+
// Show usage hint
|
|
115
|
+
if (error instanceof Error && error.message.includes('URL')) {
|
|
116
|
+
console.log(chalk.yellow('\nUsage: qa360 crawl <url> [options]'));
|
|
117
|
+
console.log(chalk.gray('Example: qa360 crawl https://example.com'));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export default createCrawlCommand;
|
package/dist/commands/history.js
CHANGED
|
@@ -71,9 +71,19 @@ export class QA360History {
|
|
|
71
71
|
// Remove .yml or .yaml extension
|
|
72
72
|
packName = fileName.replace(/\.(yml|yaml)$/, '');
|
|
73
73
|
}
|
|
74
|
+
// Format date as YYYY-MM-DD HH:mm:ss for consistency
|
|
75
|
+
const formatDate = (timestamp) => {
|
|
76
|
+
const date = new Date(timestamp);
|
|
77
|
+
const year = date.getFullYear();
|
|
78
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
79
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
80
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
81
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
82
|
+
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
83
|
+
};
|
|
74
84
|
return {
|
|
75
85
|
'Run ID': run.id.substring(0, 8),
|
|
76
|
-
'Started':
|
|
86
|
+
'Started': formatDate(run.started_at),
|
|
77
87
|
'Status': statusIcon + ' ' + run.status.toUpperCase(),
|
|
78
88
|
'Trust': run.trust_score ? `${run.trust_score}%` : '-',
|
|
79
89
|
'Duration': durationStr,
|
|
@@ -125,12 +135,23 @@ export class QA360History {
|
|
|
125
135
|
}
|
|
126
136
|
// Display formatted output
|
|
127
137
|
console.log(chalk.bold(`\n🔍 Run Details: ${fullRunId}\n`));
|
|
138
|
+
// Helper function to format date consistently
|
|
139
|
+
const formatDate = (timestamp) => {
|
|
140
|
+
const date = new Date(timestamp);
|
|
141
|
+
const year = date.getFullYear();
|
|
142
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
143
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
144
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
145
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
146
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
147
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
148
|
+
};
|
|
128
149
|
console.log(chalk.blue('📊 Overview:'));
|
|
129
150
|
console.log(` Status: ${this.formatStatus(run.status)}`);
|
|
130
151
|
console.log(` Trust Score: ${run.trust_score ? `${run.trust_score}%` : 'Not calculated'}`);
|
|
131
|
-
console.log(` Started: ${
|
|
152
|
+
console.log(` Started: ${formatDate(run.started_at)}`);
|
|
132
153
|
if (run.ended_at) {
|
|
133
|
-
console.log(` Ended: ${
|
|
154
|
+
console.log(` Ended: ${formatDate(run.ended_at)}`);
|
|
134
155
|
console.log(` Duration: ${Math.round((run.ended_at - run.started_at) / 1000)}s`);
|
|
135
156
|
}
|
|
136
157
|
console.log(` Pack: ${run.pack_path}`);
|
|
@@ -632,6 +653,20 @@ export function createHistoryCommands() {
|
|
|
632
653
|
process.exit(1);
|
|
633
654
|
}
|
|
634
655
|
});
|
|
656
|
+
historyCmd
|
|
657
|
+
.command('show')
|
|
658
|
+
.description('Show detailed run information (alias for get)')
|
|
659
|
+
.argument('<runId>', 'Run ID to retrieve')
|
|
660
|
+
.option('--json', 'Output as JSON')
|
|
661
|
+
.action(async (runId, options) => {
|
|
662
|
+
try {
|
|
663
|
+
await history.get(runId, options);
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
console.error(chalk.red('Error getting run:'), error.message);
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
635
670
|
historyCmd
|
|
636
671
|
.command('diff')
|
|
637
672
|
.description('Compare two runs')
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QA360 Init Command - Interactive pack generator
|
|
2
|
+
* QA360 Init Command - Interactive pack generator (v2)
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* qa360 init
|
|
@@ -7,9 +7,81 @@
|
|
|
7
7
|
* qa360 init --output custom-pack.yml
|
|
8
8
|
* qa360 init --beginner # Guided mode for first-time users
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export interface PackConfigV2 {
|
|
11
|
+
version: 2;
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
profile?: string;
|
|
15
|
+
auth?: AuthConfigV2;
|
|
16
|
+
gates: Record<string, GateConfigV2>;
|
|
17
|
+
hooks?: HooksConfig;
|
|
18
|
+
execution?: ExecutionConfigV2;
|
|
19
|
+
variables?: Record<string, string>;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface AuthConfigV2 {
|
|
23
|
+
api?: string;
|
|
24
|
+
ui?: string;
|
|
25
|
+
profiles?: Record<string, AuthProfile>;
|
|
26
|
+
}
|
|
27
|
+
export interface AuthProfile {
|
|
28
|
+
type: string;
|
|
29
|
+
config: Record<string, unknown>;
|
|
30
|
+
cache?: {
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
ttl?: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface GateConfigV2 {
|
|
36
|
+
adapter?: string;
|
|
37
|
+
test_files?: string[];
|
|
38
|
+
config?: Record<string, unknown>;
|
|
39
|
+
auth?: string;
|
|
40
|
+
budgets?: Record<string, unknown>;
|
|
41
|
+
options?: {
|
|
42
|
+
timeout?: number;
|
|
43
|
+
retries?: number;
|
|
44
|
+
continue_on_failure?: boolean;
|
|
45
|
+
};
|
|
46
|
+
enabled?: boolean;
|
|
47
|
+
depends_on?: string[];
|
|
48
|
+
parallel?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface HooksConfig {
|
|
51
|
+
beforeAll?: Hook[];
|
|
52
|
+
afterAll?: Hook[];
|
|
53
|
+
beforeEach?: Hook[];
|
|
54
|
+
afterEach?: Hook[];
|
|
55
|
+
}
|
|
56
|
+
export interface Hook {
|
|
57
|
+
type: 'run' | 'wait_on' | 'script' | 'docker';
|
|
58
|
+
command?: string;
|
|
59
|
+
cwd?: string;
|
|
60
|
+
timeout?: number;
|
|
61
|
+
env?: Record<string, string>;
|
|
62
|
+
wait_for?: {
|
|
63
|
+
resource: string;
|
|
64
|
+
timeout?: number;
|
|
65
|
+
};
|
|
66
|
+
compose?: {
|
|
67
|
+
file?: string;
|
|
68
|
+
services?: string[];
|
|
69
|
+
command?: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export interface ExecutionConfigV2 {
|
|
73
|
+
parallel?: boolean;
|
|
74
|
+
max_concurrency?: number;
|
|
75
|
+
default_timeout?: number;
|
|
76
|
+
default_retries?: number;
|
|
77
|
+
on_failure: 'stop' | 'continue' | 'proceed';
|
|
78
|
+
limits?: {
|
|
79
|
+
cpu?: string;
|
|
80
|
+
memory?: string;
|
|
81
|
+
};
|
|
82
|
+
compose_file?: string;
|
|
83
|
+
hook_timeout_ms?: number;
|
|
84
|
+
}
|
|
13
85
|
export interface InitOptions {
|
|
14
86
|
template?: string;
|
|
15
87
|
output?: string;
|
|
@@ -17,96 +89,18 @@ export interface InitOptions {
|
|
|
17
89
|
yes?: boolean;
|
|
18
90
|
beginner?: boolean;
|
|
19
91
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
gates: string[];
|
|
34
|
-
icon: string;
|
|
35
|
-
};
|
|
36
|
-
fullstack: {
|
|
37
|
-
name: string;
|
|
38
|
-
description: string;
|
|
39
|
-
gates: string[];
|
|
40
|
-
icon: string;
|
|
41
|
-
};
|
|
42
|
-
security: {
|
|
43
|
-
name: string;
|
|
44
|
-
description: string;
|
|
45
|
-
gates: string[];
|
|
46
|
-
icon: string;
|
|
47
|
-
};
|
|
48
|
-
complete: {
|
|
49
|
-
name: string;
|
|
50
|
-
description: string;
|
|
51
|
-
gates: string[];
|
|
52
|
-
icon: string;
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Gate descriptions with beginner-friendly explanations
|
|
57
|
-
*/
|
|
58
|
-
export declare const GATE_DESCRIPTIONS: {
|
|
59
|
-
api_smoke: {
|
|
60
|
-
short: string;
|
|
61
|
-
beginner: string;
|
|
62
|
-
example: string;
|
|
63
|
-
icon: string;
|
|
64
|
-
};
|
|
65
|
-
ui: {
|
|
66
|
-
short: string;
|
|
67
|
-
beginner: string;
|
|
68
|
-
example: string;
|
|
69
|
-
icon: string;
|
|
70
|
-
};
|
|
71
|
-
a11y: {
|
|
72
|
-
short: string;
|
|
73
|
-
beginner: string;
|
|
74
|
-
example: string;
|
|
75
|
-
icon: string;
|
|
76
|
-
};
|
|
77
|
-
perf: {
|
|
78
|
-
short: string;
|
|
79
|
-
beginner: string;
|
|
80
|
-
example: string;
|
|
81
|
-
icon: string;
|
|
82
|
-
};
|
|
83
|
-
sast: {
|
|
84
|
-
short: string;
|
|
85
|
-
beginner: string;
|
|
86
|
-
example: string;
|
|
87
|
-
icon: string;
|
|
88
|
-
};
|
|
89
|
-
dast: {
|
|
90
|
-
short: string;
|
|
91
|
-
beginner: string;
|
|
92
|
-
example: string;
|
|
93
|
-
icon: string;
|
|
94
|
-
};
|
|
95
|
-
secrets: {
|
|
96
|
-
short: string;
|
|
97
|
-
beginner: string;
|
|
98
|
-
example: string;
|
|
99
|
-
icon: string;
|
|
100
|
-
};
|
|
101
|
-
deps: {
|
|
102
|
-
short: string;
|
|
103
|
-
beginner: string;
|
|
104
|
-
example: string;
|
|
105
|
-
icon: string;
|
|
106
|
-
};
|
|
107
|
-
};
|
|
108
|
-
/**
|
|
109
|
-
* Main init command
|
|
110
|
-
*/
|
|
92
|
+
export declare const TEMPLATES: Record<string, {
|
|
93
|
+
name: string;
|
|
94
|
+
description: string;
|
|
95
|
+
gates: string[];
|
|
96
|
+
icon: string;
|
|
97
|
+
}>;
|
|
98
|
+
export declare const GATE_DESCRIPTIONS: Record<string, {
|
|
99
|
+
short: string;
|
|
100
|
+
beginner: string;
|
|
101
|
+
example: string;
|
|
102
|
+
icon: string;
|
|
103
|
+
adapter: string;
|
|
104
|
+
}>;
|
|
111
105
|
export declare function initCommand(options?: InitOptions): Promise<void>;
|
|
112
106
|
export default initCommand;
|