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.
Files changed (42) hide show
  1. package/dist/commands/ai.js +26 -14
  2. package/dist/commands/ask.d.ts +75 -23
  3. package/dist/commands/ask.js +413 -265
  4. package/dist/commands/crawl.d.ts +24 -0
  5. package/dist/commands/crawl.js +121 -0
  6. package/dist/commands/history.js +38 -3
  7. package/dist/commands/init.d.ts +89 -95
  8. package/dist/commands/init.js +282 -200
  9. package/dist/commands/run.d.ts +1 -0
  10. package/dist/core/adapters/playwright-ui.d.ts +45 -7
  11. package/dist/core/adapters/playwright-ui.js +365 -59
  12. package/dist/core/assertions/engine.d.ts +51 -0
  13. package/dist/core/assertions/engine.js +530 -0
  14. package/dist/core/assertions/index.d.ts +11 -0
  15. package/dist/core/assertions/index.js +11 -0
  16. package/dist/core/assertions/types.d.ts +121 -0
  17. package/dist/core/assertions/types.js +37 -0
  18. package/dist/core/crawler/index.d.ts +57 -0
  19. package/dist/core/crawler/index.js +281 -0
  20. package/dist/core/crawler/journey-generator.d.ts +49 -0
  21. package/dist/core/crawler/journey-generator.js +412 -0
  22. package/dist/core/crawler/page-analyzer.d.ts +88 -0
  23. package/dist/core/crawler/page-analyzer.js +709 -0
  24. package/dist/core/crawler/selector-generator.d.ts +34 -0
  25. package/dist/core/crawler/selector-generator.js +240 -0
  26. package/dist/core/crawler/types.d.ts +353 -0
  27. package/dist/core/crawler/types.js +6 -0
  28. package/dist/core/generation/crawler-pack-generator.d.ts +44 -0
  29. package/dist/core/generation/crawler-pack-generator.js +231 -0
  30. package/dist/core/generation/index.d.ts +2 -0
  31. package/dist/core/generation/index.js +2 -0
  32. package/dist/core/index.d.ts +3 -0
  33. package/dist/core/index.js +4 -0
  34. package/dist/core/types/pack-v1.d.ts +90 -0
  35. package/dist/index.js +6 -2
  36. package/examples/accessibility.yml +39 -16
  37. package/examples/api-basic.yml +19 -14
  38. package/examples/complete.yml +134 -42
  39. package/examples/fullstack.yml +66 -31
  40. package/examples/security.yml +47 -15
  41. package/examples/ui-basic.yml +16 -12
  42. 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;
@@ -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': new Date(run.started_at).toLocaleString(),
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: ${new Date(run.started_at).toLocaleString()}`);
152
+ console.log(` Started: ${formatDate(run.started_at)}`);
132
153
  if (run.ended_at) {
133
- console.log(` Ended: ${new Date(run.ended_at).toLocaleString()}`);
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')
@@ -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
- * CLI options for init command
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
- * Available templates
22
- */
23
- export declare const TEMPLATES: {
24
- 'api-basic': {
25
- name: string;
26
- description: string;
27
- gates: string[];
28
- icon: string;
29
- };
30
- 'ui-basic': {
31
- name: string;
32
- description: string;
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;