@qaguardian/playwright-score 0.1.0 → 0.1.2
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 +10 -6
- package/dist/cli.js +23 -4
- package/dist/eslint-runner.js +12 -1
- package/dist/fs-util.d.ts +6 -0
- package/dist/fs-util.js +22 -0
- package/dist/index.js +34 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,15 +18,18 @@ See [METHODOLOGY.md](./METHODOLOGY.md) for the frozen formula, or the full produ
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm install -D @qaguardian/playwright-score
|
|
21
|
-
# or run from this package
|
|
22
|
-
npm install && npm run build
|
|
23
21
|
```
|
|
24
22
|
|
|
23
|
+
Package: [@qaguardian/playwright-score](https://www.npmjs.com/package/@qaguardian/playwright-score) on npm.
|
|
24
|
+
|
|
25
25
|
## CLI
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
|
|
29
|
-
npx @qaguardian/playwright-score ./
|
|
28
|
+
# One-shot (scoped package — use -p so the playwright-score binary is resolved)
|
|
29
|
+
npx -p @qaguardian/playwright-score playwright-score ./tests --profile standard --threshold 80
|
|
30
|
+
|
|
31
|
+
# After local install
|
|
32
|
+
npx playwright-score ./flow.spec.ts --profile guardian --format json --out report.json
|
|
30
33
|
```
|
|
31
34
|
|
|
32
35
|
| Flag | Description |
|
|
@@ -35,8 +38,9 @@ npx @qaguardian/playwright-score ./flow.spec.ts --profile guardian --format json
|
|
|
35
38
|
| `--threshold <n>` | Pass bar 0–100 |
|
|
36
39
|
| `--format text\|json\|markdown\|sarif` | Output format |
|
|
37
40
|
| `--out <file>` | Write report to file |
|
|
41
|
+
| `--version` | Print version |
|
|
38
42
|
|
|
39
|
-
**Exit codes:** `0` pass · `1` below threshold · `2` tool error
|
|
43
|
+
**Exit codes:** `0` pass · `1` below threshold **or no files matched** · `2` tool error
|
|
40
44
|
|
|
41
45
|
## Library
|
|
42
46
|
|
|
@@ -56,7 +60,7 @@ console.log(result.score, result.grade, result.pass, result.findings);
|
|
|
56
60
|
|
|
57
61
|
```yaml
|
|
58
62
|
- name: Playwright Spec Score
|
|
59
|
-
run: npx playwright-score ./tests --profile standard --threshold 80 --format text
|
|
63
|
+
run: npx -p @qaguardian/playwright-score playwright-score ./tests --profile standard --threshold 80 --format text
|
|
60
64
|
```
|
|
61
65
|
|
|
62
66
|
## QA Guardian integration
|
package/dist/cli.js
CHANGED
|
@@ -10,28 +10,44 @@ import { formatText } from './formatters/text.js';
|
|
|
10
10
|
import { formatMarkdown } from './formatters/markdown.js';
|
|
11
11
|
import { formatSarif } from './formatters/sarif.js';
|
|
12
12
|
function printHelp() {
|
|
13
|
-
console.log(
|
|
13
|
+
console.log(`@qaguardian/playwright-score — deterministic Playwright spec quality score (sqs-v1)
|
|
14
14
|
|
|
15
15
|
Usage:
|
|
16
|
-
playwright-score <paths...> [options]
|
|
16
|
+
npx -p @qaguardian/playwright-score playwright-score <paths...> [options]
|
|
17
|
+
# after npm install -D @qaguardian/playwright-score:
|
|
18
|
+
npx playwright-score <paths...> [options]
|
|
17
19
|
|
|
18
20
|
Options:
|
|
19
21
|
--profile <standard|guardian> Scoring profile (default: standard)
|
|
20
22
|
--threshold <n> Pass threshold 0-100 (default: 80 standard / 75 guardian)
|
|
21
23
|
--format <text|json|markdown|sarif> Output format (default: text)
|
|
22
24
|
--out <file> Write report to file
|
|
25
|
+
--version Print version
|
|
23
26
|
--help Show help
|
|
24
27
|
|
|
25
28
|
Exit codes:
|
|
26
29
|
0 score >= threshold
|
|
27
|
-
1 score < threshold
|
|
30
|
+
1 score < threshold (or no files matched)
|
|
28
31
|
2 tool error
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
Website: https://qaguardian.com/open-source/playwright-score
|
|
34
|
+
Methodology: METHODOLOGY.md (sqs-v1)
|
|
31
35
|
`);
|
|
32
36
|
}
|
|
33
37
|
async function main() {
|
|
34
38
|
const args = process.argv.slice(2);
|
|
39
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
40
|
+
try {
|
|
41
|
+
const { createRequire } = await import('node:module');
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const pkg = require('../package.json');
|
|
44
|
+
console.log(pkg.version ?? '0.0.0');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
console.log('0.1.1');
|
|
48
|
+
}
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
35
51
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
36
52
|
printHelp();
|
|
37
53
|
process.exit(args.length === 0 ? 2 : 0);
|
|
@@ -68,6 +84,9 @@ async function main() {
|
|
|
68
84
|
else if (a === '--out') {
|
|
69
85
|
out = args[++i];
|
|
70
86
|
}
|
|
87
|
+
else if (a === '--version' || a === '-v') {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
71
90
|
else if (a.startsWith('-')) {
|
|
72
91
|
console.error(`Unknown option: ${a}`);
|
|
73
92
|
process.exit(2);
|
package/dist/eslint-runner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { ESLint } from 'eslint';
|
|
3
|
+
import { commonAncestorDir } from './fs-util.js';
|
|
3
4
|
import playwright from 'eslint-plugin-playwright';
|
|
4
5
|
import { guardianPlugin, guardianRuleConfigs, } from './rules/guardian-plugin.js';
|
|
5
6
|
import { mapRule } from './profiles.js';
|
|
@@ -67,7 +68,17 @@ function toSeverity(eslintSeverity, override) {
|
|
|
67
68
|
* Does not load host-repo eslint.config.js.
|
|
68
69
|
*/
|
|
69
70
|
export async function runEslint(options) {
|
|
70
|
-
|
|
71
|
+
// ESLint 9 flat config resolves a `basePath` from `cwd` and silently
|
|
72
|
+
// drops (as an untracked, ruleId-less message) any file that isn't
|
|
73
|
+
// underneath it — no error, no findings, just a quietly clean score.
|
|
74
|
+
// The caller's cwd (e.g. a runner process's install dir) has no reason
|
|
75
|
+
// to contain the spec files being scored, so it can never be trusted
|
|
76
|
+
// as the ESLint basePath. Derive a basePath that is guaranteed to
|
|
77
|
+
// contain every file instead; fall back to the caller's cwd only when
|
|
78
|
+
// there are no files (nothing to lint anyway).
|
|
79
|
+
const cwd = options.files.length > 0
|
|
80
|
+
? commonAncestorDir(options.files)
|
|
81
|
+
: (options.cwd ?? process.cwd());
|
|
71
82
|
const overrideConfig = buildConfig(options.profile);
|
|
72
83
|
// Try to attach typescript-eslint parser for .ts files
|
|
73
84
|
try {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deepest directory that contains every given absolute file path.
|
|
3
|
+
* Used as the ESLint flat-config basePath so linting never silently
|
|
4
|
+
* excludes files that fall outside an unrelated caller cwd.
|
|
5
|
+
*/
|
|
6
|
+
export declare function commonAncestorDir(absoluteFiles: string[]): string;
|
package/dist/fs-util.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Deepest directory that contains every given absolute file path.
|
|
4
|
+
* Used as the ESLint flat-config basePath so linting never silently
|
|
5
|
+
* excludes files that fall outside an unrelated caller cwd.
|
|
6
|
+
*/
|
|
7
|
+
export function commonAncestorDir(absoluteFiles) {
|
|
8
|
+
const dirs = absoluteFiles.map((f) => path.dirname(path.resolve(f)));
|
|
9
|
+
if (dirs.length === 0)
|
|
10
|
+
return process.cwd();
|
|
11
|
+
let common = dirs[0].split(path.sep);
|
|
12
|
+
for (const dir of dirs.slice(1)) {
|
|
13
|
+
const parts = dir.split(path.sep);
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < common.length && i < parts.length && common[i] === parts[i]) {
|
|
16
|
+
i++;
|
|
17
|
+
}
|
|
18
|
+
common = common.slice(0, i);
|
|
19
|
+
}
|
|
20
|
+
const joined = common.join(path.sep);
|
|
21
|
+
return joined || path.sep;
|
|
22
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -45,24 +45,42 @@ export async function scorePaths(options) {
|
|
|
45
45
|
const threshold = options.threshold ?? DEFAULT_THRESHOLDS[profile] ?? 80;
|
|
46
46
|
const files = expandPaths(options.paths, cwd);
|
|
47
47
|
if (files.length === 0) {
|
|
48
|
-
|
|
48
|
+
// Hard-fail: empty match must never look like a healthy suite (was ~99 PASS).
|
|
49
|
+
const findings = [
|
|
50
|
+
{
|
|
51
|
+
rule: 'playwright-score/no-files',
|
|
52
|
+
severity: 'error',
|
|
53
|
+
message: `No Playwright spec files matched: ${options.paths.join(', ') || '(no paths)'}`,
|
|
54
|
+
file: cwd,
|
|
55
|
+
dimension: 'structure',
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
return {
|
|
59
|
+
scoreVersion: 'sqs-v1',
|
|
49
60
|
profile,
|
|
61
|
+
score: 0,
|
|
62
|
+
grade: 'F',
|
|
63
|
+
pass: false,
|
|
50
64
|
threshold,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
summary: {
|
|
66
|
+
files: 0,
|
|
67
|
+
tests: 0,
|
|
68
|
+
sloc: 0,
|
|
69
|
+
findings: 1,
|
|
70
|
+
errors: 1,
|
|
71
|
+
warnings: 0,
|
|
72
|
+
nativeLocators: 0,
|
|
73
|
+
rawLocators: 0,
|
|
74
|
+
},
|
|
75
|
+
dimensions: {
|
|
76
|
+
playwrightHygiene: 0,
|
|
77
|
+
assertions: 0,
|
|
78
|
+
locators: 0,
|
|
79
|
+
structure: 0,
|
|
80
|
+
...(profile === 'guardian' ? { guardianConventions: 0 } : {}),
|
|
81
|
+
},
|
|
82
|
+
findings,
|
|
83
|
+
};
|
|
66
84
|
}
|
|
67
85
|
const eslintFindings = await runEslint({ files, profile, cwd });
|
|
68
86
|
let totalSloc = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qaguardian/playwright-score",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Deterministic, AI-free Playwright spec quality score (sqs-v1). Lint + score with standard and guardian profiles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|