@testivai/witness-playwright 1.0.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.
Files changed (61) hide show
  1. package/__tests__/.gitkeep +0 -0
  2. package/__tests__/config-integration.spec.ts +102 -0
  3. package/__tests__/snapshot.spec.d.ts +1 -0
  4. package/__tests__/snapshot.spec.js +81 -0
  5. package/__tests__/snapshot.spec.ts +58 -0
  6. package/__tests__/unit/ci.spec.d.ts +1 -0
  7. package/__tests__/unit/ci.spec.js +35 -0
  8. package/__tests__/unit/ci.spec.ts +40 -0
  9. package/__tests__/unit/reporter.spec.d.ts +1 -0
  10. package/__tests__/unit/reporter.spec.js +37 -0
  11. package/__tests__/unit/reporter.spec.ts +43 -0
  12. package/__tests__/unit/structureAnalyzer.spec.js +212 -0
  13. package/__tests__/unit/types.spec.ts +179 -0
  14. package/dist/__tests__/unit/ci.spec.d.ts +1 -0
  15. package/dist/__tests__/unit/ci.spec.js +226 -0
  16. package/dist/__tests__/unit/compression.spec.d.ts +4 -0
  17. package/dist/__tests__/unit/compression.spec.js +46 -0
  18. package/dist/ci.d.ts +30 -0
  19. package/dist/ci.js +117 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.js +47 -0
  22. package/dist/cli/init.d.ts +3 -0
  23. package/dist/cli/init.js +158 -0
  24. package/dist/config/loader.d.ts +29 -0
  25. package/dist/config/loader.js +251 -0
  26. package/dist/domAnalyzer.d.ts +10 -0
  27. package/dist/domAnalyzer.js +285 -0
  28. package/dist/index.d.ts +7 -0
  29. package/dist/index.js +11 -0
  30. package/dist/reporter-entry.d.ts +2 -0
  31. package/dist/reporter-entry.js +5 -0
  32. package/dist/reporter-types.d.ts +2 -0
  33. package/dist/reporter-types.js +2 -0
  34. package/dist/reporter.d.ts +21 -0
  35. package/dist/reporter.js +249 -0
  36. package/dist/snapshot.d.ts +12 -0
  37. package/dist/snapshot.js +601 -0
  38. package/dist/structureAnalyzer.d.ts +12 -0
  39. package/dist/structureAnalyzer.js +288 -0
  40. package/dist/types.d.ts +368 -0
  41. package/dist/types.js +10 -0
  42. package/examples/structure-analysis-example.spec.ts +118 -0
  43. package/examples/structure-analysis.config.ts +159 -0
  44. package/jest.config.js +8 -0
  45. package/package.json +51 -0
  46. package/playwright.config.ts +11 -0
  47. package/src/__tests__/unit/ci.spec.ts +257 -0
  48. package/src/__tests__/unit/compression.spec.ts +52 -0
  49. package/src/ci.ts +140 -0
  50. package/src/cli/index.ts +49 -0
  51. package/src/cli/init.ts +131 -0
  52. package/src/config/loader.ts +238 -0
  53. package/src/index.ts +14 -0
  54. package/src/reporter-entry.ts +6 -0
  55. package/src/reporter-types.ts +5 -0
  56. package/src/reporter.ts +251 -0
  57. package/src/snapshot.ts +632 -0
  58. package/src/structureAnalyzer.ts +338 -0
  59. package/src/types.ts +388 -0
  60. package/tsconfig.jest.json +7 -0
  61. package/tsconfig.json +20 -0
package/src/ci.ts ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Utilities for detecting CI/CD environments and extracting a unique run identifier.
3
+ */
4
+
5
+ /**
6
+ * CI environment information for integration feedback (e.g., GitHub commit statuses, PR comments).
7
+ */
8
+ export interface CiInfo {
9
+ /** CI provider name */
10
+ provider: string;
11
+ /** Pull/Merge request number (if available) */
12
+ prNumber?: number;
13
+ /** URL to the CI run (if available) */
14
+ runUrl?: string;
15
+ /** CI build/run identifier */
16
+ buildId?: string;
17
+ }
18
+
19
+ /**
20
+ * Gets a unique identifier for the current CI run.
21
+ * This ID is used to group snapshots from parallel shards into a single batch.
22
+ *
23
+ * @returns A unique string identifier for the CI run, or null if not in a known CI environment.
24
+ */
25
+ export function getCiRunId(): string | null {
26
+ // GitHub Actions
27
+ if (process.env.GITHUB_RUN_ID) {
28
+ return `github-${process.env.GITHUB_RUN_ID}`;
29
+ }
30
+ // GitLab CI
31
+ if (process.env.GITLAB_CI) {
32
+ return `gitlab-${process.env.CI_PIPELINE_ID}`;
33
+ }
34
+ // Jenkins
35
+ if (process.env.JENKINS_URL) {
36
+ return `jenkins-${process.env.BUILD_ID}`;
37
+ }
38
+ // CircleCI
39
+ if (process.env.CIRCLECI) {
40
+ return `circleci-${process.env.CIRCLE_WORKFLOW_ID}`;
41
+ }
42
+ // Travis CI
43
+ if (process.env.TRAVIS) {
44
+ return `travis-${process.env.TRAVIS_BUILD_ID}`;
45
+ }
46
+
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Gets detailed CI environment information including PR number and run URL.
52
+ * Used for integration feedback (GitHub commit statuses, PR comments, etc.).
53
+ *
54
+ * @returns CiInfo object if in a known CI environment, or null otherwise.
55
+ */
56
+ export function getCiInfo(): CiInfo | null {
57
+ // GitHub Actions
58
+ if (process.env.GITHUB_ACTIONS) {
59
+ const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
60
+ const repo = process.env.GITHUB_REPOSITORY;
61
+ const runId = process.env.GITHUB_RUN_ID;
62
+
63
+ // PR number: available via GITHUB_EVENT_NUMBER in pull_request events,
64
+ // or from GITHUB_REF (refs/pull/<number>/merge)
65
+ let prNumber: number | undefined;
66
+ if (process.env.GITHUB_EVENT_NUMBER) {
67
+ prNumber = parseInt(process.env.GITHUB_EVENT_NUMBER, 10) || undefined;
68
+ } else if (process.env.GITHUB_REF?.startsWith('refs/pull/')) {
69
+ const match = process.env.GITHUB_REF.match(/^refs\/pull\/(\d+)\//);
70
+ if (match) {
71
+ prNumber = parseInt(match[1], 10);
72
+ }
73
+ }
74
+
75
+ return {
76
+ provider: 'github_actions',
77
+ prNumber,
78
+ runUrl: repo && runId ? `${serverUrl}/${repo}/actions/runs/${runId}` : undefined,
79
+ buildId: runId,
80
+ };
81
+ }
82
+
83
+ // GitLab CI
84
+ if (process.env.GITLAB_CI) {
85
+ const prNumber = process.env.CI_MERGE_REQUEST_IID
86
+ ? parseInt(process.env.CI_MERGE_REQUEST_IID, 10) || undefined
87
+ : undefined;
88
+
89
+ return {
90
+ provider: 'gitlab_ci',
91
+ prNumber,
92
+ runUrl: process.env.CI_PIPELINE_URL || undefined,
93
+ buildId: process.env.CI_PIPELINE_ID,
94
+ };
95
+ }
96
+
97
+ // CircleCI
98
+ if (process.env.CIRCLECI) {
99
+ const prNumber = process.env.CIRCLE_PULL_REQUEST
100
+ ? parseInt(process.env.CIRCLE_PULL_REQUEST.split('/').pop() || '', 10) || undefined
101
+ : undefined;
102
+
103
+ return {
104
+ provider: 'circleci',
105
+ prNumber,
106
+ runUrl: process.env.CIRCLE_BUILD_URL || undefined,
107
+ buildId: process.env.CIRCLE_WORKFLOW_ID,
108
+ };
109
+ }
110
+
111
+ // Jenkins
112
+ if (process.env.JENKINS_URL) {
113
+ const prNumber = process.env.CHANGE_ID
114
+ ? parseInt(process.env.CHANGE_ID, 10) || undefined
115
+ : undefined;
116
+
117
+ return {
118
+ provider: 'jenkins',
119
+ prNumber,
120
+ runUrl: process.env.BUILD_URL || undefined,
121
+ buildId: process.env.BUILD_ID,
122
+ };
123
+ }
124
+
125
+ // Travis CI
126
+ if (process.env.TRAVIS) {
127
+ const prNumber = process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false'
128
+ ? parseInt(process.env.TRAVIS_PULL_REQUEST, 10) || undefined
129
+ : undefined;
130
+
131
+ return {
132
+ provider: 'travis_ci',
133
+ prNumber,
134
+ runUrl: process.env.TRAVIS_BUILD_WEB_URL || undefined,
135
+ buildId: process.env.TRAVIS_BUILD_ID,
136
+ };
137
+ }
138
+
139
+ return null;
140
+ }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { createConfigFile } from './init';
6
+
7
+ const program = new Command();
8
+
9
+ // Display banner
10
+ const showBanner = () => {
11
+ console.log();
12
+ console.log(chalk.cyan.bold(' TestivAI'));
13
+ console.log(chalk.gray(' Catch Visual Bugs Automatically'));
14
+ console.log(chalk.gray(' AI that catches real bugs, ignores the noise.'));
15
+ console.log();
16
+ };
17
+
18
+ program
19
+ .name('testivai')
20
+ .description('TestivAI Playwright SDK CLI')
21
+ .version(require('../../package.json').version)
22
+ .hook('preAction', () => {
23
+ if (!process.argv.includes('--quiet') && !process.argv.includes('-q')) {
24
+ showBanner();
25
+ }
26
+ });
27
+
28
+ // Global options
29
+ program
30
+ .option('-q, --quiet', 'Suppress banner output (ideal for CI)');
31
+
32
+ program
33
+ .command('init')
34
+ .description('Initialize TestivAI configuration file')
35
+ .action(async () => {
36
+ try {
37
+ await createConfigFile();
38
+ } catch (error) {
39
+ console.error('❌ Failed to initialize:', error);
40
+ process.exit(1);
41
+ }
42
+ });
43
+
44
+ program.parse(process.argv);
45
+
46
+ // Show help if no command provided
47
+ if (!process.argv.slice(2).length) {
48
+ program.outputHelp();
49
+ }
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as fs from 'fs-extra';
4
+ import * as path from 'path';
5
+
6
+ /**
7
+ * Basic CLI init script for TestivAI Playwright SDK
8
+ * Creates a testivai.config.ts file with sensible defaults
9
+ */
10
+
11
+ const DEFAULT_CONFIG = `/**
12
+ * TestivAI Configuration File
13
+ *
14
+ * This file configures how TestivAI analyzes visual differences in your tests.
15
+ *
16
+ * Layout Settings:
17
+ * - sensitivity: 0-4 scale (0=strict/precise, 4=very lenient)
18
+ * - tolerance: Base pixel tolerance for layout differences
19
+ *
20
+ * AI Settings:
21
+ * - sensitivity: 0-4 scale (0=conservative, 4=aggressive)
22
+ * - confidence: 0.0-1.0 scale (minimum confidence required for AI_BUG verdict)
23
+ *
24
+ * Custom Configuration Examples:
25
+ *
26
+ * // For strict testing (critical components):
27
+ * export default {
28
+ * layout: { sensitivity: 0, tolerance: 0.5 },
29
+ * ai: { sensitivity: 0, confidence: 0.9 }
30
+ * };
31
+ *
32
+ * // For lenient testing (dynamic content):
33
+ * export default {
34
+ * layout: { sensitivity: 4, tolerance: 3.0 },
35
+ * ai: { sensitivity: 3, confidence: 0.6 }
36
+ * };
37
+ *
38
+ * // Per-selector tolerances (advanced):
39
+ * export default {
40
+ * layout: {
41
+ * sensitivity: 2,
42
+ * tolerance: 1.0,
43
+ * selectorTolerances: {
44
+ * '.carousel': 4.0, // Carousel components shift
45
+ * '.dropdown': 2.0, // Dropdowns can vary
46
+ * '.tooltip': 5.0, // Tooltips are highly variable
47
+ * '.submit-button': 0.5 // Critical buttons need precision
48
+ * }
49
+ * },
50
+ * ai: { sensitivity: 2, confidence: 0.7 }
51
+ * };
52
+ *
53
+ * // Environment-specific overrides:
54
+ * export default {
55
+ * layout: { sensitivity: 2, tolerance: 1.0 },
56
+ * ai: { sensitivity: 2, confidence: 0.7 },
57
+ * environments: {
58
+ * ci: {
59
+ * layout: { sensitivity: 1, tolerance: 0.5 }, // Stricter in CI
60
+ * ai: { sensitivity: 1, confidence: 0.8 }
61
+ * },
62
+ * development: {
63
+ * layout: { sensitivity: 3, tolerance: 2.0 }, // More lenient locally
64
+ * ai: { sensitivity: 3, confidence: 0.6 }
65
+ * }
66
+ * }
67
+ * };
68
+ */
69
+
70
+ export default {
71
+ // Your TestivAI API key - get this from the dashboard after creating a project
72
+ apiKey: process.env.TESTIVAI_API_KEY || '',
73
+
74
+ // API endpoint - automatically points to production
75
+ apiUrl: process.env.TESTIVAI_API_URL || 'https://core-api.testiv.ai',
76
+
77
+ layout: {
78
+ sensitivity: 2, // Balanced sensitivity (0-4 scale)
79
+ tolerance: 1.0, // 1 pixel base tolerance
80
+ },
81
+ ai: {
82
+ sensitivity: 2, // Balanced AI analysis (0-4 scale)
83
+ confidence: 0.7, // 70% confidence required for AI_BUG
84
+ }
85
+ };
86
+ `;
87
+
88
+ async function createConfigFile(): Promise<void> {
89
+ const configPath = path.join(process.cwd(), 'testivai.config.ts');
90
+
91
+ // Check if config already exists
92
+ if (await fs.pathExists(configPath)) {
93
+ console.log('❌ TestivAI config already exists at:', configPath);
94
+ console.log(' Delete it first if you want to regenerate it.');
95
+ process.exit(1);
96
+ }
97
+
98
+ try {
99
+ // Create the config file
100
+ await fs.writeFile(configPath, DEFAULT_CONFIG, 'utf8');
101
+ console.log('✅ TestivAI configuration created successfully!');
102
+ console.log('📁 Config file:', configPath);
103
+ console.log('');
104
+ console.log('📖 Next steps:');
105
+ console.log(' 1. Set up your API key:');
106
+ console.log(' TESTIVAI_API_KEY=tstvai-your-key-here');
107
+ console.log('');
108
+ console.log(' 2. Update your playwright.config.ts to use TestivAI reporter:');
109
+ console.log(' reporter: [[\'@testivai/witness-playwright/reporter\']]');
110
+ console.log('');
111
+ console.log(' 3. Review and customize testivai.config.ts');
112
+ console.log(' 4. Run your tests: npx playwright test');
113
+ console.log('');
114
+ console.log('💡 Get your API key from: https://dashboard.testiv.ai');
115
+ console.log('💡 The SDK automatically connects to the production API - no URL configuration needed!');
116
+
117
+ } catch (error) {
118
+ console.error('❌ Failed to create configuration file:', error);
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ // Main execution
124
+ if (require.main === module) {
125
+ createConfigFile().catch(error => {
126
+ console.error('❌ CLI init failed:', error);
127
+ process.exit(1);
128
+ });
129
+ }
130
+
131
+ export { createConfigFile };
@@ -0,0 +1,238 @@
1
+ import * as fs from 'fs-extra';
2
+ import * as path from 'path';
3
+ import { TestivAIProjectConfig, TestivAIConfig, LayoutConfig, AIConfig } from '../types';
4
+
5
+ /**
6
+ * Default configuration when no config file is found
7
+ */
8
+ const DEFAULT_CONFIG: TestivAIProjectConfig = {
9
+ layout: {
10
+ sensitivity: 2, // Balanced sensitivity (0-4 scale)
11
+ tolerance: 1.0, // 1 pixel base tolerance
12
+ },
13
+ ai: {
14
+ sensitivity: 2, // Balanced AI analysis (0-4 scale)
15
+ confidence: 0.7, // 70% confidence required for AI_BUG
16
+ }
17
+ };
18
+
19
+ /**
20
+ * Load TestivAI configuration from file system
21
+ * Supports both .ts and .js config files
22
+ *
23
+ * @returns Promise<TestivAIProjectConfig> The loaded configuration or defaults
24
+ */
25
+ export async function loadConfig(): Promise<TestivAIProjectConfig> {
26
+ // Try TypeScript config first, then JavaScript
27
+ const tsConfigPath = path.join(process.cwd(), 'testivai.config.ts');
28
+ const jsConfigPath = path.join(process.cwd(), 'testivai.config.js');
29
+
30
+ try {
31
+ let configPath: string;
32
+ let configModule: any;
33
+
34
+ // Check for TypeScript config
35
+ if (await fs.pathExists(tsConfigPath)) {
36
+ configPath = tsConfigPath;
37
+ } else if (await fs.pathExists(jsConfigPath)) {
38
+ configPath = jsConfigPath;
39
+ } else {
40
+ console.log('⚠️ No testivai.config.ts or testivai.config.js found, using defaults');
41
+ return DEFAULT_CONFIG;
42
+ }
43
+
44
+ // Load configuration based on file type
45
+ if (configPath.endsWith('.js')) {
46
+ // For .js files, use require to get CommonJS module
47
+ // Clear require cache to ensure fresh load
48
+ delete require.cache[require.resolve(configPath)];
49
+ configModule = require(configPath);
50
+ } else {
51
+ // For .ts files, use dynamic import (ES module)
52
+ configModule = await import(configPath);
53
+ }
54
+
55
+ const config = configModule.default || configModule;
56
+
57
+ // Validate and merge with defaults
58
+ return validateAndMergeConfig(config);
59
+
60
+ } catch (error) {
61
+ console.warn('⚠️ Failed to load testivai config, using defaults:', error);
62
+ return DEFAULT_CONFIG;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Merge per-test configuration with project configuration
68
+ *
69
+ * @param projectConfig The project-level configuration
70
+ * @param testConfig Optional per-test configuration overrides
71
+ * @returns TestivAIConfig The effective configuration for this test
72
+ */
73
+ export function mergeTestConfig(
74
+ projectConfig: TestivAIProjectConfig,
75
+ testConfig?: TestivAIConfig
76
+ ): TestivAIConfig {
77
+ if (!testConfig) {
78
+ return {
79
+ layout: projectConfig.layout,
80
+ ai: projectConfig.ai,
81
+ // @renamed: dom → structure (IP protection)
82
+ structure: projectConfig.structure,
83
+ performanceMetrics: projectConfig.performanceMetrics
84
+ };
85
+ }
86
+
87
+ return {
88
+ layout: {
89
+ ...projectConfig.layout,
90
+ ...testConfig.layout
91
+ },
92
+ ai: {
93
+ ...projectConfig.ai,
94
+ ...testConfig.ai
95
+ },
96
+ // @renamed: dom → structure (IP protection)
97
+ structure: {
98
+ ...projectConfig.structure,
99
+ ...testConfig.structure
100
+ },
101
+ performanceMetrics: {
102
+ ...projectConfig.performanceMetrics,
103
+ ...testConfig.performanceMetrics
104
+ },
105
+ selectors: testConfig.selectors,
106
+ useCDP: testConfig.useCDP
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Detect current environment (CI, development, production)
112
+ *
113
+ * @returns string The detected environment
114
+ */
115
+ export function detectEnvironment(): string {
116
+ // Check for common CI environment variables
117
+ if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) {
118
+ return 'ci';
119
+ }
120
+
121
+ // Check for production indicators
122
+ if (process.env.NODE_ENV === 'production') {
123
+ return 'production';
124
+ }
125
+
126
+ // Default to development
127
+ return 'development';
128
+ }
129
+
130
+ /**
131
+ * Apply environment-specific overrides to configuration
132
+ *
133
+ * @param config The base configuration
134
+ * @returns TestivAIProjectConfig Configuration with environment overrides applied
135
+ */
136
+ export function applyEnvironmentOverrides(config: TestivAIProjectConfig): TestivAIProjectConfig {
137
+ const environment = detectEnvironment();
138
+
139
+ if (!config.environments) {
140
+ return config;
141
+ }
142
+
143
+ // Type-safe environment override access
144
+ let envOverrides: { layout?: Partial<LayoutConfig>; ai?: Partial<AIConfig> } | undefined;
145
+
146
+ switch (environment) {
147
+ case 'ci':
148
+ envOverrides = config.environments.ci;
149
+ break;
150
+ case 'development':
151
+ envOverrides = config.environments.development;
152
+ break;
153
+ case 'production':
154
+ envOverrides = config.environments.production;
155
+ break;
156
+ }
157
+
158
+ if (!envOverrides) {
159
+ return config;
160
+ }
161
+
162
+ return {
163
+ layout: {
164
+ ...config.layout,
165
+ ...envOverrides.layout
166
+ },
167
+ ai: {
168
+ ...config.ai,
169
+ ...envOverrides.ai
170
+ },
171
+ environments: config.environments // Keep the original environments config
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Validate configuration values and merge with defaults
177
+ *
178
+ * @param config The configuration to validate
179
+ * @returns TestivAIProjectConfig Validated configuration
180
+ */
181
+ function validateAndMergeConfig(config: any): TestivAIProjectConfig {
182
+ // Guard against null/undefined config
183
+ if (!config) {
184
+ console.warn('⚠️ Config is null or undefined, using defaults');
185
+ return DEFAULT_CONFIG;
186
+ }
187
+
188
+ // Basic validation
189
+ const validatedConfig: TestivAIProjectConfig = {
190
+ // API configuration with production default
191
+ apiKey: config.apiKey,
192
+ apiUrl: config.apiUrl || 'https://core-api.testiv.ai',
193
+ layout: {
194
+ sensitivity: validateRange(config.layout?.sensitivity ?? DEFAULT_CONFIG.layout.sensitivity, 0, 4, 'layout.sensitivity', DEFAULT_CONFIG.layout.sensitivity),
195
+ tolerance: validateRange(config.layout?.tolerance ?? DEFAULT_CONFIG.layout.tolerance, 0, 100, 'layout.tolerance', DEFAULT_CONFIG.layout.tolerance),
196
+ selectorTolerances: config.layout?.selectorTolerances,
197
+ useRelativeTolerance: config.layout?.useRelativeTolerance,
198
+ relativeTolerance: config.layout?.relativeTolerance
199
+ },
200
+ ai: {
201
+ sensitivity: validateRange(config.ai?.sensitivity ?? DEFAULT_CONFIG.ai.sensitivity, 0, 4, 'ai.sensitivity', DEFAULT_CONFIG.ai.sensitivity),
202
+ confidence: validateRange(config.ai?.confidence ?? DEFAULT_CONFIG.ai.confidence, 0, 1, 'ai.confidence', DEFAULT_CONFIG.ai.confidence),
203
+ enableReasoning: config.ai?.enableReasoning
204
+ },
205
+ // @renamed: dom → structure (IP protection)
206
+ structure: config.structure, // Pass through structure analysis configuration as-is
207
+ performanceMetrics: config.performanceMetrics, // Pass through performance metrics configuration as-is
208
+ environments: config.environments
209
+ };
210
+
211
+ return applyEnvironmentOverrides(validatedConfig);
212
+ }
213
+
214
+ /**
215
+ * Validate that a number is within the expected range
216
+ *
217
+ * @param value The value to validate
218
+ * @param min Minimum allowed value
219
+ * @param max Maximum allowed value
220
+ * @param field Field name for error messages
221
+ * @param defaultValue Default value to use when validation fails
222
+ * @returns number The validated value
223
+ */
224
+ function validateRange(value: any, min: number, max: number, field: string, defaultValue: number): number {
225
+ const numValue = Number(value);
226
+
227
+ if (isNaN(numValue)) {
228
+ console.warn(`⚠️ Invalid ${field}: ${value}, using default: ${defaultValue}`);
229
+ return defaultValue;
230
+ }
231
+
232
+ if (numValue < min || numValue > max) {
233
+ console.warn(`⚠️ ${field} must be between ${min} and ${max}, got ${numValue}, using default: ${defaultValue}`);
234
+ return defaultValue;
235
+ }
236
+
237
+ return numValue;
238
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { snapshot } from './snapshot';
2
+ import { getCiRunId } from './ci';
3
+
4
+ export const testivai = {
5
+ witness: snapshot,
6
+ ci: getCiRunId,
7
+ };
8
+
9
+ // Re-export types for convenience
10
+ // @renamed: DOMAnalysisConfig → StructureAnalysisConfig, DOMAnalysis → StructureAnalysis (IP protection)
11
+ export type { TestivAIConfig, TestivAIProjectConfig, StructureAnalysisConfig, StructureAnalysis } from './types';
12
+
13
+ // Structure analyzer is now handled on the backend
14
+ // The types are kept for backwards compatibility
@@ -0,0 +1,6 @@
1
+ // Entry point for Playwright reporter
2
+ // Playwright expects: module.exports = ReporterClass
3
+ import { TestivAIPlaywrightReporter } from './reporter';
4
+
5
+ // This will compile to: module.exports = TestivAIPlaywrightReporter
6
+ export = TestivAIPlaywrightReporter;
@@ -0,0 +1,5 @@
1
+ // This file isolates the Playwright-specific types for the reporter
2
+ // to allow for clean mocking in the Jest test environment.
3
+ import type { Reporter, FullConfig, Suite, FullResult } from '@playwright/test/reporter';
4
+
5
+ export type { Reporter, FullConfig, Suite, FullResult };