@cleaver/breakcheck-core 0.1.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/README.md +152 -0
- package/dist/__tests__/compare/index.test.js +322 -0
- package/dist/__tests__/compare/index.test.js.map +1 -0
- package/dist/__tests__/rules/RulesDsl.test.js +190 -0
- package/dist/__tests__/rules/RulesDsl.test.js.map +1 -0
- package/dist/__tests__/rules/RulesEngine.test.js +142 -0
- package/dist/__tests__/rules/RulesEngine.test.js.map +1 -0
- package/dist/__tests__/snapshot/index.test.js +238 -0
- package/dist/__tests__/snapshot/index.test.js.map +1 -0
- package/dist/__tests__/view/index.test.js +102 -0
- package/dist/__tests__/view/index.test.js.map +1 -0
- package/dist/api/index.js +69 -0
- package/dist/api/index.js.map +1 -0
- package/dist/core/compare/classes/ComparisonRepository.js +77 -0
- package/dist/core/compare/classes/ComparisonRepository.js.map +1 -0
- package/dist/core/compare/index.js +81 -0
- package/dist/core/compare/index.js.map +1 -0
- package/dist/core/crawler/implementations/cheerio.js +46 -0
- package/dist/core/crawler/implementations/cheerio.js.map +1 -0
- package/dist/core/crawler/implementations/playwright.js +44 -0
- package/dist/core/crawler/implementations/playwright.js.map +1 -0
- package/dist/core/crawler/index.js +61 -0
- package/dist/core/crawler/index.js.map +1 -0
- package/dist/core/rules/RulesDsl.js +251 -0
- package/dist/core/rules/RulesDsl.js.map +1 -0
- package/dist/core/rules/RulesEngine.js +91 -0
- package/dist/core/rules/RulesEngine.js.map +1 -0
- package/dist/core/snapshot/classes/LoadedSnapshot.js +37 -0
- package/dist/core/snapshot/classes/LoadedSnapshot.js.map +1 -0
- package/dist/core/snapshot/classes/SnapshotRepository.js +192 -0
- package/dist/core/snapshot/classes/SnapshotRepository.js.map +1 -0
- package/dist/core/snapshot/index.js +67 -0
- package/dist/core/snapshot/index.js.map +1 -0
- package/dist/core/view/index.handlers.js +70 -0
- package/dist/core/view/index.handlers.js.map +1 -0
- package/dist/core/view/index.js +50 -0
- package/dist/core/view/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/logger.js +26 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/root.js +25 -0
- package/dist/lib/root.js.map +1 -0
- package/dist/public/css/diff2html.min.css +1 -0
- package/dist/public/js/diff2html.min.js +1 -0
- package/dist/types/api.js +3 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/compare.js +3 -0
- package/dist/types/compare.js.map +1 -0
- package/dist/types/crawler.js +2 -0
- package/dist/types/crawler.js.map +1 -0
- package/dist/types/rules.js +6 -0
- package/dist/types/rules.js.map +1 -0
- package/dist/types/snapshot.js +3 -0
- package/dist/types/snapshot.js.map +1 -0
- package/dist/views/diff.ejs +59 -0
- package/dist/views/index.ejs +83 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Breakcheck Core
|
|
2
|
+
|
|
3
|
+
This package provides the core programmatic API for **Breakcheck**. Use this library to integrate website snapshotting, comparison, and viewing directly into your own JavaScript or TypeScript projects.
|
|
4
|
+
|
|
5
|
+
This package is intended for developers who need to build custom automation or tooling. If you're looking to use Breakcheck from the command line, see the main [`breakcheck`](https://github.com/cleaver/breakcheck) package.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install breakcheck-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## API Usage
|
|
18
|
+
|
|
19
|
+
The `breakcheck-core` library exposes a set of simple, asynchronous functions to manage the entire workflow.
|
|
20
|
+
|
|
21
|
+
### Creating a Snapshot
|
|
22
|
+
|
|
23
|
+
To begin, you need to create a "snapshot" of a website. A snapshot is a record of a site's state at a specific point in time, created by crawling the site and saving its content.
|
|
24
|
+
|
|
25
|
+
The `createSnapshotFromConfig` function takes a configuration object and returns the results of the crawl.
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
import { createSnapshotFromConfig } from "breakcheck-core";
|
|
29
|
+
|
|
30
|
+
async function takeSnapshot() {
|
|
31
|
+
const snapshotConfig = {
|
|
32
|
+
// The URL where the crawler will start
|
|
33
|
+
baseUrl: "http://localhost:3000",
|
|
34
|
+
|
|
35
|
+
// A unique name for this snapshot
|
|
36
|
+
name: "my-site-before-changes",
|
|
37
|
+
|
|
38
|
+
// Settings for the crawler
|
|
39
|
+
crawlSettings: {
|
|
40
|
+
baseUrl: "http://localhost:3000",
|
|
41
|
+
// 'cheerio' for static sites, 'playwright' for dynamic, JS-heavy sites
|
|
42
|
+
crawlerType: "cheerio",
|
|
43
|
+
maxDepth: 3, // How many links deep to follow
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Optional: Write a list of all crawled URLs to a file
|
|
47
|
+
urlListPath: "./crawled-urls.txt",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const result = await createSnapshotFromConfig(snapshotConfig);
|
|
51
|
+
|
|
52
|
+
if (result.status === "success") {
|
|
53
|
+
console.log(`✅ Snapshot created: ${result.snapshotId}`);
|
|
54
|
+
console.log(`📊 Pages crawled: ${result.pageCount}`);
|
|
55
|
+
} else {
|
|
56
|
+
console.error(`❌ Snapshot failed: ${result.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
takeSnapshot();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Listing Snapshots
|
|
64
|
+
|
|
65
|
+
You can get a list of all locally saved snapshots using the `listSnapshots` function.
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
import { listSnapshots } from "breakcheck-core";
|
|
69
|
+
|
|
70
|
+
async function showSnapshots() {
|
|
71
|
+
const snapshots = await listSnapshots();
|
|
72
|
+
|
|
73
|
+
if (snapshots.length === 0) {
|
|
74
|
+
console.log("No snapshots found.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log("Available Snapshots:");
|
|
79
|
+
snapshots.forEach((snapshot) => {
|
|
80
|
+
console.log(
|
|
81
|
+
`- ${snapshot.name} (${snapshot.pageCount} pages, created on ${snapshot.date})`
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
showSnapshots();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Running a Comparison
|
|
90
|
+
|
|
91
|
+
The core of the tool is comparing two snapshots. The `runComparison` function compares a "before" and "after" snapshot, applies rules to ignore dynamic content, and saves a detailed diff report.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
import { runComparison } from "breakcheck-core";
|
|
95
|
+
|
|
96
|
+
async function compareStates() {
|
|
97
|
+
const comparisonConfig = {
|
|
98
|
+
// The name of the 'before' snapshot
|
|
99
|
+
beforeSnapshotId: "my-site-before-changes",
|
|
100
|
+
|
|
101
|
+
// The name of the 'after' snapshot
|
|
102
|
+
afterSnapshotId: "my-site-after-changes",
|
|
103
|
+
|
|
104
|
+
// A unique name for the comparison results directory
|
|
105
|
+
comparisonName: "v1-vs-v2-comparison",
|
|
106
|
+
|
|
107
|
+
// The ruleset to apply. 'default' uses built-in baseline rules.
|
|
108
|
+
// You can also provide a path to a rules file or a ruleset object.
|
|
109
|
+
ruleset: "default",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const summary = await runComparison(comparisonConfig);
|
|
113
|
+
|
|
114
|
+
if (summary.status === "completed") {
|
|
115
|
+
console.log(`✅ Comparison complete!`);
|
|
116
|
+
console.log(` - Overall result: ${summary.overallResult.toUpperCase()}`);
|
|
117
|
+
console.log(` - Pages with differences: ${summary.pagesWithDifferences}`);
|
|
118
|
+
console.log(` - Results saved to: ${summary.resultsPath}`);
|
|
119
|
+
} else {
|
|
120
|
+
console.error("❌ Comparison failed.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
compareStates();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Viewing Comparison Results
|
|
128
|
+
|
|
129
|
+
After a comparison is complete, you can launch a local web server to view the results in your browser. The `startViewServer` function starts an Express server that renders the diffs.
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
import { startViewServer } from "breakcheck-core";
|
|
133
|
+
|
|
134
|
+
async function viewResults() {
|
|
135
|
+
const comparisonName = "v1-vs-v2-comparison"; // The name of the comparison to view
|
|
136
|
+
const port = 8080;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const server = await startViewServer(comparisonName, port);
|
|
140
|
+
console.log(`🌐 View server running at http://localhost:${port}`);
|
|
141
|
+
console.log("Press Ctrl+C to stop the server.");
|
|
142
|
+
|
|
143
|
+
// You can add logic to automatically open the browser or close the server
|
|
144
|
+
// For example, to stop it after 60 seconds:
|
|
145
|
+
// setTimeout(() => server.close(), 60000);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(`❌ Could not start view server: ${error.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
viewResults();
|
|
152
|
+
```
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { comparePage, compareSnapshots } from "../../core/compare";
|
|
3
|
+
import { ComparisonRepository } from "../../core/compare/classes/ComparisonRepository";
|
|
4
|
+
/**
|
|
5
|
+
* Creates a mock SnapshotRepository for testing
|
|
6
|
+
*/
|
|
7
|
+
class MockSnapshotRepository {
|
|
8
|
+
beforeSnapshot;
|
|
9
|
+
afterSnapshot;
|
|
10
|
+
constructor(beforeSnapshot, afterSnapshot) {
|
|
11
|
+
this.beforeSnapshot = beforeSnapshot;
|
|
12
|
+
this.afterSnapshot = afterSnapshot;
|
|
13
|
+
}
|
|
14
|
+
async loadSnapshot(name) {
|
|
15
|
+
return name === "before" ? this.beforeSnapshot : this.afterSnapshot;
|
|
16
|
+
}
|
|
17
|
+
async saveSnapshot(name, data) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
async generateUrlList(name) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
async listSnapshots() {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates a mock RulesEngine for testing
|
|
29
|
+
*/
|
|
30
|
+
class MockRulesEngine {
|
|
31
|
+
constructor(rulesetOrName) {
|
|
32
|
+
// No-op constructor
|
|
33
|
+
}
|
|
34
|
+
process(html) {
|
|
35
|
+
return html;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a mock ComparisonRepository for testing
|
|
40
|
+
*/
|
|
41
|
+
class MockComparisonRepository {
|
|
42
|
+
index = {
|
|
43
|
+
urls: {},
|
|
44
|
+
metadata: {
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
totalPages: 0,
|
|
47
|
+
pagesWithDifferences: 0,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
constructor() { }
|
|
51
|
+
static async create(name, metadata) {
|
|
52
|
+
const instance = new MockComparisonRepository();
|
|
53
|
+
instance.index.metadata = {
|
|
54
|
+
...metadata,
|
|
55
|
+
timestamp: new Date().toISOString(),
|
|
56
|
+
totalPages: 0,
|
|
57
|
+
pagesWithDifferences: 0,
|
|
58
|
+
};
|
|
59
|
+
return instance;
|
|
60
|
+
}
|
|
61
|
+
async savePageDiff(pageDiff) {
|
|
62
|
+
this.index.urls[pageDiff.url] = {
|
|
63
|
+
filename: `${pageDiff.url}.json.gz`,
|
|
64
|
+
hasDifferences: pageDiff.hasDifferences,
|
|
65
|
+
};
|
|
66
|
+
this.index.metadata.totalPages++;
|
|
67
|
+
if (pageDiff.hasDifferences) {
|
|
68
|
+
this.index.metadata.pagesWithDifferences++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async finalizeComparison() {
|
|
72
|
+
return "/mock/comparison/path";
|
|
73
|
+
}
|
|
74
|
+
getIndex() {
|
|
75
|
+
return this.index;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function createMockSnapshotRepository(beforeSnapshot, afterSnapshot) {
|
|
79
|
+
return new MockSnapshotRepository(beforeSnapshot, afterSnapshot);
|
|
80
|
+
}
|
|
81
|
+
function createMockRulesEngine() {
|
|
82
|
+
return new MockRulesEngine("default");
|
|
83
|
+
}
|
|
84
|
+
function createMockSnapshot(urls, content) {
|
|
85
|
+
return {
|
|
86
|
+
index: {
|
|
87
|
+
urls,
|
|
88
|
+
metadata: {
|
|
89
|
+
baseUrl: "https://example.com",
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
totalPages: Object.keys(urls).length,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
getPage: async (url) => ({
|
|
95
|
+
url,
|
|
96
|
+
finalUrl: url,
|
|
97
|
+
content,
|
|
98
|
+
statusCode: 200,
|
|
99
|
+
headers: { "content-type": "text/html" },
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
describe("Compare Functions", () => {
|
|
104
|
+
describe("comparePage", () => {
|
|
105
|
+
it("should detect content differences between snapshots", async () => {
|
|
106
|
+
const before = {
|
|
107
|
+
url: "https://example.com",
|
|
108
|
+
finalUrl: "https://example.com",
|
|
109
|
+
content: "<html>\n<body>Hello World</body>\n</html>",
|
|
110
|
+
statusCode: 200,
|
|
111
|
+
headers: { "content-type": "text/html" },
|
|
112
|
+
};
|
|
113
|
+
const after = {
|
|
114
|
+
url: "https://example.com",
|
|
115
|
+
finalUrl: "https://example.com",
|
|
116
|
+
content: "<html>\n<body>Hello New World</body>\n</html>",
|
|
117
|
+
statusCode: 200,
|
|
118
|
+
headers: { "content-type": "text/html" },
|
|
119
|
+
};
|
|
120
|
+
const result = await comparePage(before, after);
|
|
121
|
+
expect(result.url).toBe("https://example.com");
|
|
122
|
+
expect(result.hasDifferences).toBe(true);
|
|
123
|
+
// Expect 4 lines: One addition, one removal, two unchanged
|
|
124
|
+
expect(result.differences).toHaveLength(4);
|
|
125
|
+
const numberLinesAdded = result.differences.filter((d) => d.added).length;
|
|
126
|
+
const numberLinesRemoved = result.differences.filter((d) => d.removed).length;
|
|
127
|
+
const numberLinesUnchanged = result.differences.filter((d) => !d.added && !d.removed).length;
|
|
128
|
+
expect(numberLinesAdded).toBe(1);
|
|
129
|
+
expect(numberLinesRemoved).toBe(1);
|
|
130
|
+
expect(numberLinesUnchanged).toBe(2);
|
|
131
|
+
});
|
|
132
|
+
it("should return no differences for identical snapshots", async () => {
|
|
133
|
+
const snapshot = {
|
|
134
|
+
url: "https://example.com",
|
|
135
|
+
finalUrl: "https://example.com",
|
|
136
|
+
content: "<html><body>Hello World</body></html>",
|
|
137
|
+
statusCode: 200,
|
|
138
|
+
headers: { "content-type": "text/html" },
|
|
139
|
+
};
|
|
140
|
+
const result = await comparePage(snapshot, snapshot);
|
|
141
|
+
expect(result.url).toBe("https://example.com");
|
|
142
|
+
expect(result.hasDifferences).toBe(false);
|
|
143
|
+
expect(result.differences).toHaveLength(1);
|
|
144
|
+
expect(result.differences[0].added).toBeUndefined();
|
|
145
|
+
expect(result.differences[0].removed).toBeUndefined();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
describe("compareSnapshots", () => {
|
|
149
|
+
async function createSetup(beforeUrls, afterUrls, beforeContent, afterContent) {
|
|
150
|
+
const beforeSnapshot = createMockSnapshot(beforeUrls, beforeContent);
|
|
151
|
+
const afterSnapshot = createMockSnapshot(afterUrls, afterContent);
|
|
152
|
+
const snapshotRepository = await createMockSnapshotRepository(beforeSnapshot, afterSnapshot);
|
|
153
|
+
const comparisonRepository = await ComparisonRepository.create("test-comparison", {
|
|
154
|
+
beforeSnapshotId: "before",
|
|
155
|
+
afterSnapshotId: "after",
|
|
156
|
+
rulesUsedIdentifier: "default",
|
|
157
|
+
});
|
|
158
|
+
const rulesEngine = createMockRulesEngine();
|
|
159
|
+
return {
|
|
160
|
+
snapshotRepository,
|
|
161
|
+
comparisonRepository,
|
|
162
|
+
rulesEngine,
|
|
163
|
+
comparisonParams: {
|
|
164
|
+
beforeSnapshotId: "before",
|
|
165
|
+
afterSnapshotId: "after",
|
|
166
|
+
comparisonName: "test-comparison",
|
|
167
|
+
ruleset: "default",
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
it("should detect content differences between snapshots", async () => {
|
|
172
|
+
const beforeUrls = {
|
|
173
|
+
"https://example.com": {
|
|
174
|
+
filename: "example.html",
|
|
175
|
+
statusCode: 200,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const afterUrls = {
|
|
179
|
+
"https://example.com": {
|
|
180
|
+
filename: "example.html",
|
|
181
|
+
statusCode: 200,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const beforeContent = "<html>\n<body>Hello World</body>\n</html>";
|
|
185
|
+
const afterContent = "<html>\n<body>Hello New World</body>\n</html>";
|
|
186
|
+
const setup = await createSetup(beforeUrls, afterUrls, beforeContent, afterContent);
|
|
187
|
+
const result = await compareSnapshots(setup.comparisonParams, setup.snapshotRepository, setup.comparisonRepository, setup.rulesEngine);
|
|
188
|
+
expect(result.status).toBe("completed");
|
|
189
|
+
expect(result.overallResult).toBe("fail");
|
|
190
|
+
expect(result.totalPagesCompared).toBe(1);
|
|
191
|
+
expect(result.pagesWithDifferences).toBe(1);
|
|
192
|
+
expect(result.newUrls).toHaveLength(0);
|
|
193
|
+
expect(result.removedUrls).toHaveLength(0);
|
|
194
|
+
});
|
|
195
|
+
it("should detect new and removed URLs between snapshots", async () => {
|
|
196
|
+
const beforeUrls = {
|
|
197
|
+
"https://example.com/old": {
|
|
198
|
+
filename: "old.html",
|
|
199
|
+
statusCode: 200,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
const afterUrls = {
|
|
203
|
+
"https://example.com/new": {
|
|
204
|
+
filename: "new.html",
|
|
205
|
+
statusCode: 200,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
const beforeContent = "<html><body>Old Page</body></html>";
|
|
209
|
+
const afterContent = "<html><body>New Page</body></html>";
|
|
210
|
+
const setup = await createSetup(beforeUrls, afterUrls, beforeContent, afterContent);
|
|
211
|
+
const result = await compareSnapshots(setup.comparisonParams, setup.snapshotRepository, setup.comparisonRepository, setup.rulesEngine);
|
|
212
|
+
expect(result.status).toBe("completed");
|
|
213
|
+
expect(result.overallResult).toBe("pass");
|
|
214
|
+
expect(result.totalPagesCompared).toBe(0);
|
|
215
|
+
expect(result.pagesWithDifferences).toBe(0);
|
|
216
|
+
expect(result.newUrls).toHaveLength(1);
|
|
217
|
+
expect(result.newUrls[0]).toBe("https://example.com/new");
|
|
218
|
+
expect(result.removedUrls).toHaveLength(1);
|
|
219
|
+
expect(result.removedUrls[0]).toBe("https://example.com/old");
|
|
220
|
+
});
|
|
221
|
+
it("should filter URLs when specified", async () => {
|
|
222
|
+
const beforeUrls = {
|
|
223
|
+
"https://example.com/page1": {
|
|
224
|
+
filename: "page1.html",
|
|
225
|
+
statusCode: 200,
|
|
226
|
+
},
|
|
227
|
+
"https://example.com/page2": {
|
|
228
|
+
filename: "page2.html",
|
|
229
|
+
statusCode: 200,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
const afterUrls = {
|
|
233
|
+
"https://example.com/page1": {
|
|
234
|
+
filename: "page1.html",
|
|
235
|
+
statusCode: 200,
|
|
236
|
+
},
|
|
237
|
+
"https://example.com/page2": {
|
|
238
|
+
filename: "page2.html",
|
|
239
|
+
statusCode: 200,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
const beforeContent = "<html><body>Original Content</body></html>";
|
|
243
|
+
const afterContent = "<html><body>Modified Content</body></html>";
|
|
244
|
+
const setup = await createSetup(beforeUrls, afterUrls, beforeContent, afterContent);
|
|
245
|
+
const result = await compareSnapshots({
|
|
246
|
+
...setup.comparisonParams,
|
|
247
|
+
urls: ["https://example.com/page1"],
|
|
248
|
+
}, setup.snapshotRepository, setup.comparisonRepository, setup.rulesEngine);
|
|
249
|
+
expect(result.status).toBe("completed");
|
|
250
|
+
expect(result.overallResult).toBe("fail");
|
|
251
|
+
expect(result.totalPagesCompared).toBe(1);
|
|
252
|
+
expect(result.pagesWithDifferences).toBe(1);
|
|
253
|
+
expect(result.newUrls).toHaveLength(0);
|
|
254
|
+
expect(result.removedUrls).toHaveLength(0);
|
|
255
|
+
});
|
|
256
|
+
it("should return no differences for identical snapshots", async () => {
|
|
257
|
+
const beforeUrls = {
|
|
258
|
+
"https://example.com": {
|
|
259
|
+
filename: "example.html",
|
|
260
|
+
statusCode: 200,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
const afterUrls = {
|
|
264
|
+
"https://example.com": {
|
|
265
|
+
filename: "example.html",
|
|
266
|
+
statusCode: 200,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
const beforeContent = "<html><body>Hello World</body></html>";
|
|
270
|
+
const afterContent = "<html><body>Hello World</body></html>";
|
|
271
|
+
const setup = await createSetup(beforeUrls, afterUrls, beforeContent, afterContent);
|
|
272
|
+
const result = await compareSnapshots(setup.comparisonParams, setup.snapshotRepository, setup.comparisonRepository, setup.rulesEngine);
|
|
273
|
+
expect(result.status).toBe("completed");
|
|
274
|
+
expect(result.overallResult).toBe("pass");
|
|
275
|
+
expect(result.totalPagesCompared).toBe(1);
|
|
276
|
+
expect(result.pagesWithDifferences).toBe(0);
|
|
277
|
+
expect(result.newUrls).toHaveLength(0);
|
|
278
|
+
expect(result.removedUrls).toHaveLength(0);
|
|
279
|
+
});
|
|
280
|
+
it("should return URLs in ascending sorted order regardless of input order", async () => {
|
|
281
|
+
const beforeUrls = {
|
|
282
|
+
"https://example.com/m": {
|
|
283
|
+
filename: "m.html",
|
|
284
|
+
statusCode: 200,
|
|
285
|
+
},
|
|
286
|
+
"https://example.com/a": {
|
|
287
|
+
filename: "a.html",
|
|
288
|
+
statusCode: 200,
|
|
289
|
+
},
|
|
290
|
+
"https://example.com/z": {
|
|
291
|
+
filename: "z.html",
|
|
292
|
+
statusCode: 200,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
const afterUrls = {
|
|
296
|
+
"https://example.com/m": {
|
|
297
|
+
filename: "m.html",
|
|
298
|
+
statusCode: 200,
|
|
299
|
+
},
|
|
300
|
+
"https://example.com/a": {
|
|
301
|
+
filename: "a.html",
|
|
302
|
+
statusCode: 200,
|
|
303
|
+
},
|
|
304
|
+
"https://example.com/z": {
|
|
305
|
+
filename: "z.html",
|
|
306
|
+
statusCode: 200,
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
const beforeContent = "<html><body>Original Content</body></html>";
|
|
310
|
+
const afterContent = "<html><body>Modified Content</body></html>";
|
|
311
|
+
const setup = await createSetup(beforeUrls, afterUrls, beforeContent, afterContent);
|
|
312
|
+
const result = await compareSnapshots(setup.comparisonParams, setup.snapshotRepository, setup.comparisonRepository, setup.rulesEngine);
|
|
313
|
+
expect(result.status).toBe("completed");
|
|
314
|
+
expect(result.overallResult).toBe("fail");
|
|
315
|
+
expect(result.totalPagesCompared).toBe(3);
|
|
316
|
+
expect(result.pagesWithDifferences).toBe(3);
|
|
317
|
+
expect(result.newUrls).toHaveLength(0);
|
|
318
|
+
expect(result.removedUrls).toHaveLength(0);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../../src/__tests__/compare/index.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { processRulesDsl } from "../../core/rules/RulesDsl";
|
|
5
|
+
// Mock fs and path modules
|
|
6
|
+
vi.mock("fs", () => ({
|
|
7
|
+
readFileSync: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
vi.mock("path", () => ({
|
|
10
|
+
join: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
// Mock the findRootDir function to return a predictable path
|
|
13
|
+
vi.mock("../../lib/root", () => ({
|
|
14
|
+
findRootDir: vi.fn().mockResolvedValue("/mock/root"),
|
|
15
|
+
}));
|
|
16
|
+
describe("RulesDsl", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
join.mockImplementation((...args) => args.join("/"));
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.resetAllMocks();
|
|
23
|
+
});
|
|
24
|
+
describe("processRulesDsl", () => {
|
|
25
|
+
it("should throw error if rules file not found", async () => {
|
|
26
|
+
readFileSync.mockImplementation(() => {
|
|
27
|
+
throw new Error("File not found");
|
|
28
|
+
});
|
|
29
|
+
await expect(processRulesDsl("non-existent")).rejects.toThrow("Rules file not found: rules/non-existent/rules.breakcheck");
|
|
30
|
+
});
|
|
31
|
+
it("should parse single action rules", async () => {
|
|
32
|
+
const rulesContent = `
|
|
33
|
+
css:.ad-container do: exclude
|
|
34
|
+
css:#session-id do: exclude
|
|
35
|
+
css:.important-note do: include content_regex:"Warning:"
|
|
36
|
+
`;
|
|
37
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
38
|
+
const result = await processRulesDsl("test-rules");
|
|
39
|
+
expect(result).toEqual({
|
|
40
|
+
name: "test-rules",
|
|
41
|
+
rules: [
|
|
42
|
+
{
|
|
43
|
+
selector: ".ad-container",
|
|
44
|
+
actions: [{ action: "exclude", modifiers: {} }],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
selector: "#session-id",
|
|
48
|
+
actions: [{ action: "exclude", modifiers: {} }],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
selector: ".important-note",
|
|
52
|
+
actions: [
|
|
53
|
+
{
|
|
54
|
+
action: "include",
|
|
55
|
+
modifiers: { content_regex: "Warning:" },
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it("should parse action blocks", async () => {
|
|
63
|
+
const rulesContent = `
|
|
64
|
+
css:img do
|
|
65
|
+
remove_attr attr:"srcset"
|
|
66
|
+
remove_attr attr:"sizes"
|
|
67
|
+
rewrite_attr attr:"src" regex:"//cdn\\d+\\.example\\.com/" replace:"//cdn.example.com/"
|
|
68
|
+
end
|
|
69
|
+
`;
|
|
70
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
71
|
+
const result = await processRulesDsl("test-rules");
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
name: "test-rules",
|
|
74
|
+
rules: [
|
|
75
|
+
{
|
|
76
|
+
selector: "img",
|
|
77
|
+
actions: [
|
|
78
|
+
{ action: "remove_attr", modifiers: { attr: "srcset" } },
|
|
79
|
+
{ action: "remove_attr", modifiers: { attr: "sizes" } },
|
|
80
|
+
{
|
|
81
|
+
action: "rewrite_attr",
|
|
82
|
+
modifiers: {
|
|
83
|
+
attr: "src",
|
|
84
|
+
regex: "//cdn\\d+\\.example\\.com/",
|
|
85
|
+
replace: "//cdn.example.com/",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
it("should parse rewrite content rules", async () => {
|
|
94
|
+
const rulesContent = `
|
|
95
|
+
css:.timestamp do: rewrite_content regex:"\\d{2}/\\d{2}/\\d{4}" replace:"DATE_STAMP"
|
|
96
|
+
css:.view-count do: rewrite_content regex:"\\d{1,3}(,\\d{3})* views" replace:"VIEW_COUNT views"
|
|
97
|
+
`;
|
|
98
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
99
|
+
const result = await processRulesDsl("test-rules");
|
|
100
|
+
expect(result).toEqual({
|
|
101
|
+
name: "test-rules",
|
|
102
|
+
rules: [
|
|
103
|
+
{
|
|
104
|
+
selector: ".timestamp",
|
|
105
|
+
actions: [
|
|
106
|
+
{
|
|
107
|
+
action: "rewrite_content",
|
|
108
|
+
modifiers: {
|
|
109
|
+
regex: "\\d{2}/\\d{2}/\\d{4}",
|
|
110
|
+
replace: "DATE_STAMP",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
selector: ".view-count",
|
|
117
|
+
actions: [
|
|
118
|
+
{
|
|
119
|
+
action: "rewrite_content",
|
|
120
|
+
modifiers: {
|
|
121
|
+
regex: "\\d{1,3}(,\\d{3})* views",
|
|
122
|
+
replace: "VIEW_COUNT views",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
it("should handle comments and whitespace", async () => {
|
|
131
|
+
const rulesContent = `
|
|
132
|
+
-- This is a comment
|
|
133
|
+
css:.ad-container do: exclude
|
|
134
|
+
|
|
135
|
+
-- Another comment
|
|
136
|
+
css:.important-note do: include content_regex:"Warning:"
|
|
137
|
+
`;
|
|
138
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
139
|
+
const result = await processRulesDsl("test-rules");
|
|
140
|
+
expect(result).toEqual({
|
|
141
|
+
name: "test-rules",
|
|
142
|
+
rules: [
|
|
143
|
+
{
|
|
144
|
+
selector: ".ad-container",
|
|
145
|
+
actions: [{ action: "exclude", modifiers: {} }],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
selector: ".important-note",
|
|
149
|
+
actions: [
|
|
150
|
+
{
|
|
151
|
+
action: "include",
|
|
152
|
+
modifiers: { content_regex: "Warning:" },
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
it("should throw error for invalid syntax", async () => {
|
|
160
|
+
const rulesContent = `
|
|
161
|
+
css:.ad-container do exclude # Missing colon after do
|
|
162
|
+
`;
|
|
163
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
164
|
+
await expect(processRulesDsl("test-rules")).rejects.toThrow("Syntax errors in rules file");
|
|
165
|
+
});
|
|
166
|
+
it("should throw error for invalid action", async () => {
|
|
167
|
+
const rulesContent = `
|
|
168
|
+
css:.ad-container do: invalid_action
|
|
169
|
+
`;
|
|
170
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
171
|
+
await expect(processRulesDsl("test-rules")).rejects.toThrow("Syntax errors in rules file");
|
|
172
|
+
});
|
|
173
|
+
it("should throw error for unclosed action block", async () => {
|
|
174
|
+
const rulesContent = `
|
|
175
|
+
css:img do
|
|
176
|
+
remove_attr attr:"srcset"
|
|
177
|
+
`;
|
|
178
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
179
|
+
await expect(processRulesDsl("test-rules")).rejects.toThrow("Syntax errors in rules file");
|
|
180
|
+
});
|
|
181
|
+
it("should throw error for missing required modifiers", async () => {
|
|
182
|
+
const rulesContent = `
|
|
183
|
+
css:img do: remove_attr
|
|
184
|
+
`;
|
|
185
|
+
readFileSync.mockReturnValue(rulesContent);
|
|
186
|
+
await expect(processRulesDsl("test-rules")).rejects.toThrow("Syntax errors in rules file");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
//# sourceMappingURL=RulesDsl.test.js.map
|