prowl-tools 0.1.3
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/LICENSE +190 -0
- package/NOTICE +63 -0
- package/README.md +945 -0
- package/dist/chunk-NXXGJOBG.js +546 -0
- package/dist/chunk-NXXGJOBG.js.map +1 -0
- package/dist/chunk-T7YLXF6X.js +3158 -0
- package/dist/chunk-T7YLXF6X.js.map +1 -0
- package/dist/index.cjs +5143 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1366 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.cjs +3765 -0
- package/dist/lib.cjs.map +1 -0
- package/dist/lib.d.cts +935 -0
- package/dist/lib.d.ts +935 -0
- package/dist/lib.js +55 -0
- package/dist/lib.js.map +1 -0
- package/dist/loader-5RDNTJHH.js +27 -0
- package/dist/loader-5RDNTJHH.js.map +1 -0
- package/dist/visual-FSARM2JS.js +46 -0
- package/dist/visual-FSARM2JS.js.map +1 -0
- package/examples/config.yml +41 -0
- package/examples/hunts/hello.yml +20 -0
- package/package.json +83 -0
package/dist/lib.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_FLAKY_THRESHOLD,
|
|
3
|
+
analyzePage,
|
|
4
|
+
buildHealCandidates,
|
|
5
|
+
clusterFailures,
|
|
6
|
+
computeFlakeScore,
|
|
7
|
+
extractFailures,
|
|
8
|
+
extractSelectorIntent,
|
|
9
|
+
generateHunt,
|
|
10
|
+
healSelector,
|
|
11
|
+
interpolateHunt,
|
|
12
|
+
rankFlaky,
|
|
13
|
+
readHistory,
|
|
14
|
+
readHuntHistory,
|
|
15
|
+
runHunt,
|
|
16
|
+
runSuite,
|
|
17
|
+
updateBacklogFromSuite
|
|
18
|
+
} from "./chunk-T7YLXF6X.js";
|
|
19
|
+
import {
|
|
20
|
+
configSchema,
|
|
21
|
+
huntSchema,
|
|
22
|
+
listHunts,
|
|
23
|
+
loadConfig,
|
|
24
|
+
loadHunt,
|
|
25
|
+
loadHuntMeta,
|
|
26
|
+
loadHuntTags,
|
|
27
|
+
stepSchema
|
|
28
|
+
} from "./chunk-NXXGJOBG.js";
|
|
29
|
+
export {
|
|
30
|
+
DEFAULT_FLAKY_THRESHOLD,
|
|
31
|
+
analyzePage,
|
|
32
|
+
buildHealCandidates,
|
|
33
|
+
clusterFailures,
|
|
34
|
+
computeFlakeScore,
|
|
35
|
+
configSchema,
|
|
36
|
+
extractFailures,
|
|
37
|
+
extractSelectorIntent,
|
|
38
|
+
generateHunt,
|
|
39
|
+
healSelector,
|
|
40
|
+
huntSchema,
|
|
41
|
+
interpolateHunt,
|
|
42
|
+
listHunts,
|
|
43
|
+
loadConfig,
|
|
44
|
+
loadHunt,
|
|
45
|
+
loadHuntMeta,
|
|
46
|
+
loadHuntTags,
|
|
47
|
+
rankFlaky,
|
|
48
|
+
readHistory,
|
|
49
|
+
readHuntHistory,
|
|
50
|
+
runHunt,
|
|
51
|
+
runSuite,
|
|
52
|
+
stepSchema,
|
|
53
|
+
updateBacklogFromSuite
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=lib.js.map
|
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFIG_DIR,
|
|
3
|
+
LEGACY_CONFIG_DIR,
|
|
4
|
+
ensureAllowedDomain,
|
|
5
|
+
findConfigPath,
|
|
6
|
+
listHunts,
|
|
7
|
+
loadConfig,
|
|
8
|
+
loadHunt,
|
|
9
|
+
loadHuntMeta,
|
|
10
|
+
loadHuntTags,
|
|
11
|
+
resolveViewport,
|
|
12
|
+
warnLegacyConfigDir
|
|
13
|
+
} from "./chunk-NXXGJOBG.js";
|
|
14
|
+
export {
|
|
15
|
+
CONFIG_DIR,
|
|
16
|
+
LEGACY_CONFIG_DIR,
|
|
17
|
+
ensureAllowedDomain,
|
|
18
|
+
findConfigPath,
|
|
19
|
+
listHunts,
|
|
20
|
+
loadConfig,
|
|
21
|
+
loadHunt,
|
|
22
|
+
loadHuntMeta,
|
|
23
|
+
loadHuntTags,
|
|
24
|
+
resolveViewport,
|
|
25
|
+
warnLegacyConfigDir
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=loader-5RDNTJHH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/runner/visual.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { PNG } from "pngjs";
|
|
5
|
+
import pixelmatch from "pixelmatch";
|
|
6
|
+
async function compareScreenshots(baselinePath, currentPath, diffPath, threshold) {
|
|
7
|
+
const baselineData = PNG.sync.read(fs.readFileSync(baselinePath));
|
|
8
|
+
const currentData = PNG.sync.read(fs.readFileSync(currentPath));
|
|
9
|
+
const { width, height } = baselineData;
|
|
10
|
+
if (currentData.width !== width || currentData.height !== height) {
|
|
11
|
+
const diff2 = new PNG({ width, height });
|
|
12
|
+
fs.writeFileSync(diffPath, PNG.sync.write(diff2));
|
|
13
|
+
return {
|
|
14
|
+
match: false,
|
|
15
|
+
diffPercentage: 1,
|
|
16
|
+
diffImagePath: diffPath
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const diff = new PNG({ width, height });
|
|
20
|
+
const diffPixels = pixelmatch(
|
|
21
|
+
baselineData.data,
|
|
22
|
+
currentData.data,
|
|
23
|
+
diff.data,
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
{ threshold: 0.1 }
|
|
27
|
+
);
|
|
28
|
+
const totalPixels = width * height;
|
|
29
|
+
const diffPercentage = diffPixels / totalPixels;
|
|
30
|
+
fs.writeFileSync(diffPath, PNG.sync.write(diff));
|
|
31
|
+
return {
|
|
32
|
+
match: diffPercentage <= threshold,
|
|
33
|
+
diffPercentage,
|
|
34
|
+
diffImagePath: diffPath
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function ensureBaselineDir(configDir) {
|
|
38
|
+
const baselineDir = path.join(configDir, "baselines");
|
|
39
|
+
fs.mkdirSync(baselineDir, { recursive: true });
|
|
40
|
+
return baselineDir;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
compareScreenshots,
|
|
44
|
+
ensureBaselineDir
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=visual-FSARM2JS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runner/visual.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { PNG } from \"pngjs\";\nimport pixelmatch from \"pixelmatch\";\n\nexport type ComparisonResult = {\n match: boolean;\n diffPercentage: number;\n diffImagePath?: string;\n};\n\nexport async function compareScreenshots(\n baselinePath: string,\n currentPath: string,\n diffPath: string,\n threshold: number\n): Promise<ComparisonResult> {\n const baselineData = PNG.sync.read(fs.readFileSync(baselinePath));\n const currentData = PNG.sync.read(fs.readFileSync(currentPath));\n\n const { width, height } = baselineData;\n\n if (currentData.width !== width || currentData.height !== height) {\n const diff = new PNG({ width, height });\n fs.writeFileSync(diffPath, PNG.sync.write(diff));\n return {\n match: false,\n diffPercentage: 1,\n diffImagePath: diffPath\n };\n }\n\n const diff = new PNG({ width, height });\n const diffPixels = pixelmatch(\n baselineData.data,\n currentData.data,\n diff.data,\n width,\n height,\n { threshold: 0.1 }\n );\n\n const totalPixels = width * height;\n const diffPercentage = diffPixels / totalPixels;\n\n fs.writeFileSync(diffPath, PNG.sync.write(diff));\n\n return {\n match: diffPercentage <= threshold,\n diffPercentage,\n diffImagePath: diffPath\n };\n}\n\nexport function ensureBaselineDir(configDir: string): string {\n const baselineDir = path.join(configDir, \"baselines\");\n fs.mkdirSync(baselineDir, { recursive: true });\n return baselineDir;\n}\n"],"mappings":";AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,WAAW;AACpB,OAAO,gBAAgB;AAQvB,eAAsB,mBACpB,cACA,aACA,UACA,WAC2B;AAC3B,QAAM,eAAe,IAAI,KAAK,KAAK,GAAG,aAAa,YAAY,CAAC;AAChE,QAAM,cAAc,IAAI,KAAK,KAAK,GAAG,aAAa,WAAW,CAAC;AAE9D,QAAM,EAAE,OAAO,OAAO,IAAI;AAE1B,MAAI,YAAY,UAAU,SAAS,YAAY,WAAW,QAAQ;AAChE,UAAMA,QAAO,IAAI,IAAI,EAAE,OAAO,OAAO,CAAC;AACtC,OAAG,cAAc,UAAU,IAAI,KAAK,MAAMA,KAAI,CAAC;AAC/C,WAAO;AAAA,MACL,OAAO;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,OAAO,IAAI,IAAI,EAAE,OAAO,OAAO,CAAC;AACtC,QAAM,aAAa;AAAA,IACjB,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,WAAW,IAAI;AAAA,EACnB;AAEA,QAAM,cAAc,QAAQ;AAC5B,QAAM,iBAAiB,aAAa;AAEpC,KAAG,cAAc,UAAU,IAAI,KAAK,MAAM,IAAI,CAAC;AAE/C,SAAO;AAAA,IACL,OAAO,kBAAkB;AAAA,IACzB;AAAA,IACA,eAAe;AAAA,EACjB;AACF;AAEO,SAAS,kBAAkB,WAA2B;AAC3D,QAAM,cAAc,KAAK,KAAK,WAAW,WAAW;AACpD,KAAG,UAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC7C,SAAO;AACT;","names":["diff"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Prowl Configuration
|
|
2
|
+
# This file controls how Prowl runs your hunts.
|
|
3
|
+
# All options shown with their default values.
|
|
4
|
+
|
|
5
|
+
# Target application — the base URL for all hunt navigation
|
|
6
|
+
target:
|
|
7
|
+
url: "http://localhost:3000"
|
|
8
|
+
|
|
9
|
+
# Browser settings
|
|
10
|
+
browser:
|
|
11
|
+
headless: true # Set to false or use --headed to see the browser
|
|
12
|
+
slowMo: 0 # Milliseconds to wait between actions (useful for debugging)
|
|
13
|
+
timeout: 30000 # Default timeout for page operations (30s)
|
|
14
|
+
|
|
15
|
+
# Artifact generation — what gets saved per run
|
|
16
|
+
artifacts:
|
|
17
|
+
screenshots: "on-failure" # "on-failure" or "all" (screenshot every step)
|
|
18
|
+
networkHar: false # Save network activity as HAR file
|
|
19
|
+
console: true # Save browser console output
|
|
20
|
+
|
|
21
|
+
# Hunt-level assertions — applied after every hunt's steps complete
|
|
22
|
+
assertions:
|
|
23
|
+
noConsoleErrors: true # Fail if browser console has errors
|
|
24
|
+
noNetworkErrors: true # Fail if any HTTP response >= 400
|
|
25
|
+
maxTotalTimeMs: 30000 # Max total time for all steps (30s)
|
|
26
|
+
networkIgnorePatterns: [] # URL patterns to ignore for network error checks
|
|
27
|
+
|
|
28
|
+
# Safety guardrails — prevent accidental damage
|
|
29
|
+
guardrails:
|
|
30
|
+
maxSteps: 50 # Maximum steps per hunt
|
|
31
|
+
allowedDomains: # Only navigate to these domains
|
|
32
|
+
- "localhost"
|
|
33
|
+
- "127.0.0.1"
|
|
34
|
+
- "0.0.0.0"
|
|
35
|
+
forbiddenSelectors: # Selectors that steps cannot interact with
|
|
36
|
+
- "[data-danger]"
|
|
37
|
+
- ".delete-btn"
|
|
38
|
+
|
|
39
|
+
# Auth state — captured by `prowl login`, used to skip login in hunts
|
|
40
|
+
auth:
|
|
41
|
+
storageStatePath: ".prowl/auth-state.json"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Hello Hunt — Your First Prowl Test
|
|
2
|
+
# ---
|
|
3
|
+
# This minimal hunt verifies your target app is running and the page loads.
|
|
4
|
+
# Customize the URL path and expected text to match your app.
|
|
5
|
+
#
|
|
6
|
+
# Run it: prowl run hello
|
|
7
|
+
#
|
|
8
|
+
# Browse more hunt templates at https://hub.prowl.tools
|
|
9
|
+
|
|
10
|
+
name: hello
|
|
11
|
+
description: Verify the target app loads successfully
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- navigate: "/"
|
|
15
|
+
|
|
16
|
+
- waitForNetworkIdle:
|
|
17
|
+
timeout: 5000
|
|
18
|
+
|
|
19
|
+
assertions:
|
|
20
|
+
- noConsoleErrors: true
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "prowl-tools",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "CLI-first QA testing tool for deterministic Playwright flows.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"author": "Michael Tookes",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/prowl-tools/prowl.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://prowl.tools",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/prowl-tools/prowl/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"testing",
|
|
18
|
+
"qa",
|
|
19
|
+
"playwright",
|
|
20
|
+
"yaml",
|
|
21
|
+
"e2e",
|
|
22
|
+
"browser-testing",
|
|
23
|
+
"automation",
|
|
24
|
+
"web-testing",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"main": "dist/lib.cjs",
|
|
28
|
+
"module": "dist/lib.js",
|
|
29
|
+
"types": "dist/lib.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/lib.d.ts",
|
|
34
|
+
"default": "./dist/lib.js"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/lib.d.cts",
|
|
38
|
+
"default": "./dist/lib.cjs"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"bin": {
|
|
43
|
+
"prowl": "dist/index.js"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"examples",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"README.md",
|
|
50
|
+
"NOTICE"
|
|
51
|
+
],
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=20.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"lint": "eslint .",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
63
|
+
"chalk": "^5.3.0",
|
|
64
|
+
"commander": "^12.1.0",
|
|
65
|
+
"dotenv": "^16.6.1",
|
|
66
|
+
"ora": "^8.1.1",
|
|
67
|
+
"pixelmatch": "^7.1.0",
|
|
68
|
+
"playwright": "^1.50.1",
|
|
69
|
+
"pngjs": "^7.0.0",
|
|
70
|
+
"yaml": "^2.6.1",
|
|
71
|
+
"zod": "^3.23.8"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@types/node": "^22.13.1",
|
|
75
|
+
"@types/pngjs": "^6.0.5",
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
77
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
78
|
+
"eslint": "^8.57.1",
|
|
79
|
+
"tsup": "^8.3.5",
|
|
80
|
+
"typescript": "^5.7.3",
|
|
81
|
+
"vitest": "^2.1.8"
|
|
82
|
+
}
|
|
83
|
+
}
|