@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 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
- npx @qaguardian/playwright-score ./tests --profile standard --threshold 80
29
- npx @qaguardian/playwright-score ./flow.spec.ts --profile guardian --format json --out report.json
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(`playwright-score — deterministic Playwright spec quality score (sqs-v1)
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
- See METHODOLOGY.md for sqs-v1 formula.
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);
@@ -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
- const cwd = options.cwd ?? process.cwd();
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;
@@ -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
- return computeScore({
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
- findings: [
52
- {
53
- rule: 'playwright-score/no-files',
54
- severity: 'error',
55
- message: 'No Playwright spec files matched the given paths',
56
- file: cwd,
57
- dimension: 'structure',
58
- },
59
- ],
60
- sloc: 1,
61
- files: 0,
62
- tests: 0,
63
- nativeLocators: 0,
64
- rawLocators: 0,
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.0",
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",