@vizzly-testing/cli 0.20.0 → 0.20.1-beta.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/dist/api/client.js +134 -0
- package/dist/api/core.js +341 -0
- package/dist/api/endpoints.js +314 -0
- package/dist/api/index.js +19 -0
- package/dist/auth/client.js +91 -0
- package/dist/auth/core.js +176 -0
- package/dist/auth/index.js +30 -0
- package/dist/auth/operations.js +148 -0
- package/dist/cli.js +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/finalize.js +41 -15
- package/dist/commands/login.js +7 -6
- package/dist/commands/logout.js +4 -4
- package/dist/commands/project.js +5 -4
- package/dist/commands/run.js +158 -90
- package/dist/commands/status.js +22 -18
- package/dist/commands/tdd.js +105 -78
- package/dist/commands/upload.js +61 -26
- package/dist/commands/whoami.js +4 -4
- package/dist/config/core.js +438 -0
- package/dist/config/index.js +13 -0
- package/dist/config/operations.js +327 -0
- package/dist/index.js +1 -1
- package/dist/project/core.js +295 -0
- package/dist/project/index.js +13 -0
- package/dist/project/operations.js +393 -0
- package/dist/report-generator/core.js +315 -0
- package/dist/report-generator/index.js +8 -0
- package/dist/report-generator/operations.js +196 -0
- package/dist/reporter/reporter-bundle.iife.js +16 -16
- package/dist/screenshot-server/core.js +157 -0
- package/dist/screenshot-server/index.js +11 -0
- package/dist/screenshot-server/operations.js +183 -0
- package/dist/sdk/index.js +3 -2
- package/dist/server/handlers/api-handler.js +14 -5
- package/dist/server/handlers/tdd-handler.js +80 -48
- package/dist/server-manager/core.js +183 -0
- package/dist/server-manager/index.js +81 -0
- package/dist/server-manager/operations.js +208 -0
- package/dist/services/build-manager.js +2 -69
- package/dist/services/index.js +21 -48
- package/dist/services/screenshot-server.js +40 -74
- package/dist/services/server-manager.js +45 -80
- package/dist/services/static-report-generator.js +21 -163
- package/dist/services/test-runner.js +90 -250
- package/dist/services/uploader.js +56 -358
- package/dist/tdd/core/hotspot-coverage.js +112 -0
- package/dist/tdd/core/signature.js +101 -0
- package/dist/tdd/index.js +19 -0
- package/dist/tdd/metadata/baseline-metadata.js +103 -0
- package/dist/tdd/metadata/hotspot-metadata.js +93 -0
- package/dist/tdd/services/baseline-downloader.js +151 -0
- package/dist/tdd/services/baseline-manager.js +166 -0
- package/dist/tdd/services/comparison-service.js +230 -0
- package/dist/tdd/services/hotspot-service.js +71 -0
- package/dist/tdd/services/result-service.js +123 -0
- package/dist/tdd/tdd-service.js +1081 -0
- package/dist/test-runner/core.js +255 -0
- package/dist/test-runner/index.js +13 -0
- package/dist/test-runner/operations.js +483 -0
- package/dist/uploader/core.js +396 -0
- package/dist/uploader/index.js +11 -0
- package/dist/uploader/operations.js +412 -0
- package/package.json +7 -12
- package/dist/services/api-service.js +0 -412
- package/dist/services/auth-service.js +0 -226
- package/dist/services/config-service.js +0 -369
- package/dist/services/html-report-generator.js +0 -455
- package/dist/services/project-service.js +0 -326
- package/dist/services/report-generator/report.css +0 -411
- package/dist/services/report-generator/viewer.js +0 -102
- package/dist/services/tdd-service.js +0 -1437
|
@@ -1,99 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Screenshot Server Service
|
|
3
3
|
* Listens for and processes screenshots from the test runner
|
|
4
|
+
*
|
|
5
|
+
* This class is a thin wrapper around the functional operations in
|
|
6
|
+
* src/screenshot-server/. It maintains backwards compatibility while
|
|
7
|
+
* delegating to pure functions for testability.
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import { createServer } from 'node:http';
|
|
7
11
|
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
12
|
+
import { handleRequest, parseRequestBody, startServer, stopServer } from '../screenshot-server/index.js';
|
|
8
13
|
import * as output from '../utils/output.js';
|
|
9
14
|
export class ScreenshotServer {
|
|
10
|
-
constructor(config, buildManager) {
|
|
15
|
+
constructor(config, buildManager, options = {}) {
|
|
11
16
|
this.config = config;
|
|
12
17
|
this.buildManager = buildManager;
|
|
13
18
|
this.server = null;
|
|
19
|
+
|
|
20
|
+
// Dependency injection for testing
|
|
21
|
+
this.deps = options.deps || {
|
|
22
|
+
createHttpServer: createServer,
|
|
23
|
+
output,
|
|
24
|
+
createError: (message, code) => new VizzlyError(message, code)
|
|
25
|
+
};
|
|
14
26
|
}
|
|
15
27
|
async start() {
|
|
16
|
-
this.server =
|
|
17
|
-
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
});
|
|
28
|
+
this.server = await startServer({
|
|
29
|
+
config: this.config,
|
|
30
|
+
requestHandler: this.handleRequest.bind(this),
|
|
31
|
+
deps: {
|
|
32
|
+
createHttpServer: this.deps.createHttpServer,
|
|
33
|
+
createError: this.deps.createError,
|
|
34
|
+
output: this.deps.output
|
|
35
|
+
}
|
|
26
36
|
});
|
|
27
37
|
}
|
|
28
38
|
async stop() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
}
|
|
39
|
+
await stopServer({
|
|
40
|
+
server: this.server,
|
|
41
|
+
deps: {
|
|
42
|
+
output: this.deps.output
|
|
43
|
+
}
|
|
44
|
+
});
|
|
37
45
|
}
|
|
38
46
|
async handleRequest(req, res) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
properties
|
|
47
|
-
} = body;
|
|
48
|
-
if (!name || !image) {
|
|
49
|
-
res.statusCode = 400;
|
|
50
|
-
res.end(JSON.stringify({
|
|
51
|
-
error: 'name and image are required'
|
|
52
|
-
}));
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Use default buildId if none provided
|
|
57
|
-
const effectiveBuildId = buildId || 'default';
|
|
58
|
-
await this.buildManager.addScreenshot(effectiveBuildId, {
|
|
59
|
-
name,
|
|
60
|
-
image,
|
|
61
|
-
properties
|
|
62
|
-
});
|
|
63
|
-
res.statusCode = 200;
|
|
64
|
-
res.end(JSON.stringify({
|
|
65
|
-
success: true
|
|
66
|
-
}));
|
|
67
|
-
} catch (error) {
|
|
68
|
-
output.error('Failed to process screenshot:', error);
|
|
69
|
-
res.statusCode = 500;
|
|
70
|
-
res.end(JSON.stringify({
|
|
71
|
-
error: 'Internal server error'
|
|
72
|
-
}));
|
|
47
|
+
await handleRequest({
|
|
48
|
+
req,
|
|
49
|
+
res,
|
|
50
|
+
deps: {
|
|
51
|
+
buildManager: this.buildManager,
|
|
52
|
+
createError: this.deps.createError,
|
|
53
|
+
output: this.deps.output
|
|
73
54
|
}
|
|
74
|
-
}
|
|
75
|
-
res.statusCode = 404;
|
|
76
|
-
res.end(JSON.stringify({
|
|
77
|
-
error: 'Not found'
|
|
78
|
-
}));
|
|
79
|
-
}
|
|
55
|
+
});
|
|
80
56
|
}
|
|
81
57
|
async parseRequestBody(req) {
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
req.on('end', () => {
|
|
88
|
-
try {
|
|
89
|
-
resolve(JSON.parse(body));
|
|
90
|
-
} catch {
|
|
91
|
-
reject(new VizzlyError('Invalid JSON in request body', 'INVALID_JSON'));
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
req.on('error', error => {
|
|
95
|
-
reject(new VizzlyError(`Request error: ${error.message}`, 'REQUEST_ERROR'));
|
|
96
|
-
});
|
|
58
|
+
return parseRequestBody({
|
|
59
|
+
req,
|
|
60
|
+
deps: {
|
|
61
|
+
createError: this.deps.createError
|
|
62
|
+
}
|
|
97
63
|
});
|
|
98
64
|
}
|
|
99
65
|
}
|
|
@@ -1,108 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server Manager Service
|
|
3
3
|
* Manages the HTTP server with functional handlers
|
|
4
|
+
*
|
|
5
|
+
* This class is a thin wrapper around the functional operations in
|
|
6
|
+
* src/server-manager/. It maintains backwards compatibility while
|
|
7
|
+
* delegating to pure functions for testability.
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
|
-
import {
|
|
11
|
+
import { createApiClient } from '../api/index.js';
|
|
8
12
|
import { createApiHandler } from '../server/handlers/api-handler.js';
|
|
9
13
|
import { createTddHandler } from '../server/handlers/tdd-handler.js';
|
|
10
14
|
import { createHttpServer } from '../server/http-server.js';
|
|
15
|
+
import { buildServerInterface, getTddResults, startServer, stopServer } from '../server-manager/index.js';
|
|
11
16
|
export class ServerManager {
|
|
12
17
|
constructor(config, options = {}) {
|
|
13
18
|
this.config = config;
|
|
14
19
|
this.httpServer = null;
|
|
15
20
|
this.handler = null;
|
|
16
21
|
this.services = options.services || {};
|
|
22
|
+
this.tddMode = false;
|
|
23
|
+
|
|
24
|
+
// Dependency injection for testing - defaults to real implementations
|
|
25
|
+
this.deps = options.deps || {
|
|
26
|
+
createHttpServer,
|
|
27
|
+
createTddHandler,
|
|
28
|
+
createApiHandler,
|
|
29
|
+
createApiClient,
|
|
30
|
+
fs: {
|
|
31
|
+
mkdirSync,
|
|
32
|
+
writeFileSync,
|
|
33
|
+
existsSync,
|
|
34
|
+
unlinkSync
|
|
35
|
+
}
|
|
36
|
+
};
|
|
17
37
|
}
|
|
18
38
|
async start(buildId = null, tddMode = false, setBaseline = false) {
|
|
19
39
|
this.buildId = buildId;
|
|
20
40
|
this.tddMode = tddMode;
|
|
21
41
|
this.setBaseline = setBaseline;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Pass buildId and tddService in services so http-server can use them
|
|
32
|
-
const servicesWithExtras = {
|
|
33
|
-
...this.services,
|
|
34
|
-
buildId: this.buildId,
|
|
35
|
-
// Expose tddService for baseline download operations (TDD mode only)
|
|
36
|
-
tddService: this.tddMode ? this.handler.tddService : null
|
|
37
|
-
};
|
|
38
|
-
this.httpServer = createHttpServer(port, this.handler, servicesWithExtras);
|
|
39
|
-
if (this.httpServer) {
|
|
40
|
-
await this.httpServer.start();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Write server info to .vizzly/server.json for SDK discovery
|
|
44
|
-
// This allows SDKs that can't access environment variables (like Swift/iOS)
|
|
45
|
-
// to discover both the server port and current build ID
|
|
46
|
-
try {
|
|
47
|
-
const vizzlyDir = join(process.cwd(), '.vizzly');
|
|
48
|
-
mkdirSync(vizzlyDir, {
|
|
49
|
-
recursive: true
|
|
50
|
-
});
|
|
51
|
-
const serverFile = join(vizzlyDir, 'server.json');
|
|
52
|
-
const serverInfo = {
|
|
53
|
-
port: port.toString(),
|
|
54
|
-
pid: process.pid,
|
|
55
|
-
startTime: Date.now()
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Include buildId if we have one (for `vizzly run` mode)
|
|
59
|
-
if (this.buildId) {
|
|
60
|
-
serverInfo.buildId = this.buildId;
|
|
61
|
-
}
|
|
62
|
-
writeFileSync(serverFile, JSON.stringify(serverInfo, null, 2));
|
|
63
|
-
} catch {
|
|
64
|
-
// Non-fatal - SDK can still use health check or environment variables
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async createApiService() {
|
|
68
|
-
if (!this.config.apiKey) return null;
|
|
69
|
-
const {
|
|
70
|
-
ApiService
|
|
71
|
-
} = await import('./api-service.js');
|
|
72
|
-
return new ApiService({
|
|
73
|
-
...this.config,
|
|
74
|
-
command: 'run'
|
|
42
|
+
let result = await startServer({
|
|
43
|
+
config: this.config,
|
|
44
|
+
buildId,
|
|
45
|
+
tddMode,
|
|
46
|
+
setBaseline,
|
|
47
|
+
projectRoot: process.cwd(),
|
|
48
|
+
services: this.services,
|
|
49
|
+
deps: this.deps
|
|
75
50
|
});
|
|
51
|
+
this.httpServer = result.httpServer;
|
|
52
|
+
this.handler = result.handler;
|
|
76
53
|
}
|
|
77
54
|
async stop() {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} catch {
|
|
85
|
-
// Don't throw - cleanup errors shouldn't fail the stop process
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Clean up server.json so the client SDK doesn't try to connect to a dead server
|
|
90
|
-
try {
|
|
91
|
-
const serverFile = join(process.cwd(), '.vizzly', 'server.json');
|
|
92
|
-
if (existsSync(serverFile)) {
|
|
93
|
-
unlinkSync(serverFile);
|
|
94
|
-
}
|
|
95
|
-
} catch {
|
|
96
|
-
// Non-fatal - cleanup errors shouldn't fail the stop process
|
|
97
|
-
}
|
|
55
|
+
await stopServer({
|
|
56
|
+
httpServer: this.httpServer,
|
|
57
|
+
handler: this.handler,
|
|
58
|
+
projectRoot: process.cwd(),
|
|
59
|
+
deps: this.deps
|
|
60
|
+
});
|
|
98
61
|
}
|
|
99
62
|
|
|
100
63
|
// Expose server interface for compatibility
|
|
101
64
|
get server() {
|
|
102
|
-
return {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
};
|
|
65
|
+
return buildServerInterface({
|
|
66
|
+
handler: this.handler,
|
|
67
|
+
httpServer: this.httpServer
|
|
68
|
+
});
|
|
106
69
|
}
|
|
107
70
|
|
|
108
71
|
/**
|
|
@@ -110,7 +73,9 @@ export class ServerManager {
|
|
|
110
73
|
* Only available in TDD mode after tests have run
|
|
111
74
|
*/
|
|
112
75
|
async getTddResults() {
|
|
113
|
-
|
|
114
|
-
|
|
76
|
+
return getTddResults({
|
|
77
|
+
tddMode: this.tddMode,
|
|
78
|
+
handler: this.handler
|
|
79
|
+
});
|
|
115
80
|
}
|
|
116
81
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Static Report Generator using React Reporter
|
|
3
3
|
* Generates a self-contained HTML file with the React dashboard and embedded data
|
|
4
|
+
*
|
|
5
|
+
* This class is a thin wrapper around the functional report-generator module.
|
|
6
|
+
* For new code, consider using the functions directly from '../report-generator/'.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
import { existsSync } from 'node:fs';
|
|
7
10
|
import { copyFile, mkdir, writeFile } from 'node:fs/promises';
|
|
8
11
|
import { dirname, join } from 'node:path';
|
|
9
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { buildFallbackHtmlContent, buildHtmlContent, buildReportDir, buildReportPath, generateReport } from '../report-generator/index.js';
|
|
10
14
|
import * as output from '../utils/output.js';
|
|
11
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
16
|
const __dirname = dirname(__filename);
|
|
@@ -15,8 +19,8 @@ export class StaticReportGenerator {
|
|
|
15
19
|
constructor(workingDir, config) {
|
|
16
20
|
this.workingDir = workingDir;
|
|
17
21
|
this.config = config;
|
|
18
|
-
this.reportDir =
|
|
19
|
-
this.reportPath =
|
|
22
|
+
this.reportDir = buildReportDir(workingDir);
|
|
23
|
+
this.reportPath = buildReportPath(this.reportDir);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -25,100 +29,29 @@ export class StaticReportGenerator {
|
|
|
25
29
|
* @returns {Promise<string>} Path to generated report
|
|
26
30
|
*/
|
|
27
31
|
async generateReport(reportData) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const cssPath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.css');
|
|
40
|
-
if (!existsSync(bundlePath) || !existsSync(cssPath)) {
|
|
41
|
-
throw new Error('Reporter bundles not found. Run "npm run build:reporter" first.');
|
|
32
|
+
return generateReport({
|
|
33
|
+
reportData,
|
|
34
|
+
workingDir: this.workingDir,
|
|
35
|
+
projectRoot: PROJECT_ROOT,
|
|
36
|
+
deps: {
|
|
37
|
+
mkdir,
|
|
38
|
+
existsSync,
|
|
39
|
+
copyFile,
|
|
40
|
+
writeFile,
|
|
41
|
+
output,
|
|
42
|
+
getDate: () => new Date()
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
// Copy bundles to report directory for self-contained report
|
|
45
|
-
await copyFile(bundlePath, join(this.reportDir, 'reporter-bundle.js'));
|
|
46
|
-
await copyFile(cssPath, join(this.reportDir, 'reporter-bundle.css'));
|
|
47
|
-
|
|
48
|
-
// Generate HTML with embedded data
|
|
49
|
-
const htmlContent = this.generateHtmlTemplate(reportData);
|
|
50
|
-
await writeFile(this.reportPath, htmlContent, 'utf8');
|
|
51
|
-
output.debug('report', 'generated static report');
|
|
52
|
-
return this.reportPath;
|
|
53
|
-
} catch (error) {
|
|
54
|
-
output.error(`Failed to generate static report: ${error.message}`);
|
|
55
|
-
throw new Error(`Report generation failed: ${error.message}`);
|
|
56
|
-
}
|
|
44
|
+
});
|
|
57
45
|
}
|
|
58
46
|
|
|
59
47
|
/**
|
|
60
48
|
* Generate HTML template with embedded React app
|
|
61
49
|
* @param {Object} reportData - Report data to embed
|
|
62
50
|
* @returns {string} HTML content
|
|
51
|
+
* @deprecated Use buildHtmlContent from report-generator/core.js
|
|
63
52
|
*/
|
|
64
53
|
generateHtmlTemplate(reportData) {
|
|
65
|
-
|
|
66
|
-
const serializedData = JSON.stringify(reportData).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026');
|
|
67
|
-
return `<!DOCTYPE html>
|
|
68
|
-
<html lang="en">
|
|
69
|
-
<head>
|
|
70
|
-
<meta charset="UTF-8">
|
|
71
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
72
|
-
<title>Vizzly Dev Report - ${new Date().toLocaleString()}</title>
|
|
73
|
-
<link rel="stylesheet" href="./reporter-bundle.css">
|
|
74
|
-
<style>
|
|
75
|
-
/* Loading spinner styles */
|
|
76
|
-
.reporter-loading {
|
|
77
|
-
display: flex;
|
|
78
|
-
align-items: center;
|
|
79
|
-
justify-content: center;
|
|
80
|
-
min-height: 100vh;
|
|
81
|
-
background: #0f172a;
|
|
82
|
-
color: #f59e0b;
|
|
83
|
-
}
|
|
84
|
-
.spinner {
|
|
85
|
-
width: 48px;
|
|
86
|
-
height: 48px;
|
|
87
|
-
border: 4px solid rgba(245, 158, 11, 0.2);
|
|
88
|
-
border-top-color: #f59e0b;
|
|
89
|
-
border-radius: 50%;
|
|
90
|
-
animation: spin 1s linear infinite;
|
|
91
|
-
margin-bottom: 1rem;
|
|
92
|
-
}
|
|
93
|
-
@keyframes spin {
|
|
94
|
-
to { transform: rotate(360deg); }
|
|
95
|
-
}
|
|
96
|
-
</style>
|
|
97
|
-
</head>
|
|
98
|
-
<body>
|
|
99
|
-
<div id="vizzly-reporter-root">
|
|
100
|
-
<div class="reporter-loading">
|
|
101
|
-
<div style="text-align: center;">
|
|
102
|
-
<div class="spinner"></div>
|
|
103
|
-
<p>Loading Vizzly Report...</p>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
<script>
|
|
109
|
-
// Embedded report data (static mode)
|
|
110
|
-
window.VIZZLY_REPORTER_DATA = ${serializedData};
|
|
111
|
-
window.VIZZLY_STATIC_MODE = true;
|
|
112
|
-
|
|
113
|
-
// Generate timestamp for "generated at" display
|
|
114
|
-
window.VIZZLY_REPORT_GENERATED_AT = "${new Date().toISOString()}";
|
|
115
|
-
|
|
116
|
-
console.log('Vizzly Static Report loaded');
|
|
117
|
-
console.log('Report data:', window.VIZZLY_REPORTER_DATA?.summary);
|
|
118
|
-
</script>
|
|
119
|
-
<script src="./reporter-bundle.js"></script>
|
|
120
|
-
</body>
|
|
121
|
-
</html>`;
|
|
54
|
+
return buildHtmlContent(reportData, new Date());
|
|
122
55
|
}
|
|
123
56
|
|
|
124
57
|
/**
|
|
@@ -127,81 +60,6 @@ export class StaticReportGenerator {
|
|
|
127
60
|
* @returns {string} Minimal HTML content
|
|
128
61
|
*/
|
|
129
62
|
generateFallbackHtml(reportData) {
|
|
130
|
-
|
|
131
|
-
const comparisons = reportData.comparisons || [];
|
|
132
|
-
const failed = comparisons.filter(c => c.status === 'failed');
|
|
133
|
-
return `<!DOCTYPE html>
|
|
134
|
-
<html lang="en">
|
|
135
|
-
<head>
|
|
136
|
-
<meta charset="UTF-8">
|
|
137
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
138
|
-
<title>Vizzly Dev Report</title>
|
|
139
|
-
<style>
|
|
140
|
-
body {
|
|
141
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
142
|
-
background: #0f172a;
|
|
143
|
-
color: #e2e8f0;
|
|
144
|
-
padding: 2rem;
|
|
145
|
-
}
|
|
146
|
-
.container { max-width: 1200px; margin: 0 auto; }
|
|
147
|
-
.header { text-align: center; margin-bottom: 2rem; }
|
|
148
|
-
.summary {
|
|
149
|
-
display: flex;
|
|
150
|
-
gap: 2rem;
|
|
151
|
-
justify-content: center;
|
|
152
|
-
margin: 2rem 0;
|
|
153
|
-
}
|
|
154
|
-
.stat { text-align: center; }
|
|
155
|
-
.stat-number {
|
|
156
|
-
font-size: 3rem;
|
|
157
|
-
font-weight: bold;
|
|
158
|
-
display: block;
|
|
159
|
-
}
|
|
160
|
-
.warning {
|
|
161
|
-
background: #fef3c7;
|
|
162
|
-
color: #92400e;
|
|
163
|
-
padding: 1rem;
|
|
164
|
-
border-radius: 0.5rem;
|
|
165
|
-
margin: 2rem 0;
|
|
166
|
-
}
|
|
167
|
-
</style>
|
|
168
|
-
</head>
|
|
169
|
-
<body>
|
|
170
|
-
<div class="container">
|
|
171
|
-
<div class="header">
|
|
172
|
-
<h1>🐻 Vizzly Dev Report</h1>
|
|
173
|
-
<p>Generated: ${new Date().toLocaleString()}</p>
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
|
-
<div class="summary">
|
|
177
|
-
<div class="stat">
|
|
178
|
-
<span class="stat-number">${summary.total || 0}</span>
|
|
179
|
-
<span>Total</span>
|
|
180
|
-
</div>
|
|
181
|
-
<div class="stat">
|
|
182
|
-
<span class="stat-number" style="color: #10b981;">${summary.passed || 0}</span>
|
|
183
|
-
<span>Passed</span>
|
|
184
|
-
</div>
|
|
185
|
-
<div class="stat">
|
|
186
|
-
<span class="stat-number" style="color: #ef4444;">${summary.failed || 0}</span>
|
|
187
|
-
<span>Failed</span>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
<div class="warning">
|
|
192
|
-
<strong>⚠️ Limited Report</strong>
|
|
193
|
-
<p>This is a fallback report. For the full interactive experience, ensure the reporter bundle is built:</p>
|
|
194
|
-
<code>npm run build:reporter</code>
|
|
195
|
-
</div>
|
|
196
|
-
|
|
197
|
-
${failed.length > 0 ? `
|
|
198
|
-
<h2>Failed Comparisons</h2>
|
|
199
|
-
<ul>
|
|
200
|
-
${failed.map(c => `<li>${c.name} - ${c.diffPercentage || 0}% difference</li>`).join('')}
|
|
201
|
-
</ul>
|
|
202
|
-
` : '<p style="text-align: center; font-size: 1.5rem;">✅ All tests passed!</p>'}
|
|
203
|
-
</div>
|
|
204
|
-
</body>
|
|
205
|
-
</html>`;
|
|
63
|
+
return buildFallbackHtmlContent(reportData, new Date());
|
|
206
64
|
}
|
|
207
65
|
}
|