tewip-ai 0.17.3 → 0.18.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.
@@ -0,0 +1,90 @@
1
+ import { truncate } from './signature.js';
2
+ /** The module name each runner prints when it cannot load something. */
3
+ const PATTERNS = [
4
+ { language: 'Python', pattern: /ModuleNotFoundError:\s*No module named ['"]([^'"]+)['"]/ },
5
+ { language: 'Node', pattern: /Cannot find module ['"]([^'"]+)['"]/ },
6
+ { language: 'Node', pattern: /ERR_MODULE_NOT_FOUND[^'"]*['"]([^'"]+)['"]/ },
7
+ { language: 'Java', pattern: /(?:NoClassDefFoundError|ClassNotFoundException):\s*([\w.$]+)/ },
8
+ { language: 'Ruby', pattern: /cannot load such file --\s*(\S+)/ },
9
+ { language: 'Go', pattern: /cannot find package ['"]([^'"]+)['"]/ },
10
+ { language: '.NET', pattern: /Could not load file or assembly ['"]([^'",]+)/ },
11
+ { language: 'PHP', pattern: /Failed opening required ['"]([^'"]+)['"]|Class ['"]([^'"]+)['"] not found/ },
12
+ ];
13
+ // `ImportError: cannot import name 'x'` is deliberately not here. It usually means the code asked
14
+ // for something that does not exist - a rename that missed a caller - which is a real bug, and only
15
+ // sometimes a version mismatch. "No module named" says plainly that nothing could be found.
16
+ /** A module that is part of this project rather than something it depends on. */
17
+ const LOOKS_INTERNAL = /^\.|^(?:src|tests?|app|lib|internal|pkg|main)\b|_test$|\.spec$|\.test$/i;
18
+ /** The file could be found but not understood, which is a mistake in the code, not the machine. */
19
+ const A_CODE_ERROR = /\b(?:SyntaxError|IndentationError|TabError|TypeError|NameError|AttributeError|error TS\d+|Unexpected token)\b/;
20
+ /**
21
+ * pytest reports a missing plugin as a missing fixture, which on its own is ambiguous: a typo in a
22
+ * fixture name reads exactly the same. What separates them is how many tests it hits. A typo breaks
23
+ * the one test that made it; an uninstalled plugin breaks every test that asks for its fixture.
24
+ *
25
+ * Found on humanize's real suite, where 15 tests failed with "fixture 'benchmark' not found". The
26
+ * model answered those 15 identical failures with 13 ENV and 2 TEST_DATA - the same input getting
27
+ * different answers, which is worse than either answer, and exactly what a rule is for.
28
+ */
29
+ const MISSING_FIXTURE = /fixture ['"]([^'"]+)['"] not found/;
30
+ export const FIXTURE_SHARED_BY = 3;
31
+ export const fixtureMissing = (errorMessage) => MISSING_FIXTURE.exec(errorMessage)?.[1] ?? null;
32
+ export function missingDependency(errorMessage) {
33
+ if (A_CODE_ERROR.test(errorMessage))
34
+ return null;
35
+ for (const { language, pattern } of PATTERNS) {
36
+ const found = pattern.exec(errorMessage);
37
+ const module = found?.slice(1).find(Boolean);
38
+ if (!module)
39
+ continue;
40
+ if (LOOKS_INTERNAL.test(module))
41
+ return null;
42
+ return { language, module };
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * Nothing about the test ran, so nothing about the test or the code has been shown. The suite needs
48
+ * a dependency the machine does not have.
49
+ */
50
+ export const envMissingDependency = {
51
+ id: 'env-missing-dependency',
52
+ evaluate({ record, run }) {
53
+ const fixture = fixtureMissing(record.errorMessage);
54
+ if (fixture) {
55
+ const sharing = run.failures.filter((f) => fixtureMissing(f.errorMessage) === fixture).length;
56
+ if (sharing < FIXTURE_SHARED_BY)
57
+ return null; // one test only: a typo is as likely, so say nothing
58
+ return {
59
+ category: 'ENV',
60
+ confidence: 0.85,
61
+ evidence: [
62
+ {
63
+ source: 'env-missing-dependency',
64
+ message: `${sharing} tests in this run all need the fixture "${truncate(fixture, 40)}" and none can find it: the plugin that provides it is not installed, so no test code ran`,
65
+ data: { fixture, sharing },
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ const found = missingDependency(record.errorMessage);
71
+ if (!found)
72
+ return null;
73
+ // When the whole run failed this way, the install step is the obvious suspect and there is
74
+ // nothing test-specific left to doubt.
75
+ const wholeRun = run.failures.length >= 3 && run.failures.every((f) => missingDependency(f.errorMessage) !== null);
76
+ return {
77
+ category: 'ENV',
78
+ confidence: wholeRun ? 0.92 : 0.85,
79
+ evidence: [
80
+ {
81
+ source: 'env-missing-dependency',
82
+ message: wholeRun
83
+ ? `every failure in this run is a module that could not be loaded (${found.language}: "${truncate(found.module, 60)}"): the dependencies were not installed, so no test code ran`
84
+ : `the test could not be loaded because ${found.language} could not find "${truncate(found.module, 60)}": a dependency is missing, so nothing about the test itself has been shown`,
85
+ data: { ...found, wholeRun },
86
+ },
87
+ ],
88
+ };
89
+ },
90
+ };
@@ -2,6 +2,7 @@ import { deviceEnvSignal } from './device-errors.js';
2
2
  import { INFRA_MEANING, infraSignal } from './infra-errors.js';
3
3
  import { isElementNotFound, networkSignal, valueMismatch } from './error-parse.js';
4
4
  import { locatorDrift, locatorDriftStrict } from './drift-rules.js';
5
+ import { envMissingDependency } from './missing-dependency.js';
5
6
  import { timingFlaky, timingIntercepted, timingIntermittent, timingKnownFlaky, timingRetryPass, timingStaleElement } from './timing-rules.js';
6
7
  import { errorScenarioOf } from './llm/context.js';
7
8
  import { errorShape, truncate } from './signature.js';
@@ -191,4 +192,4 @@ const realBugAssertion = {
191
192
  * Priority order: the first matching rule decides. Signals from outside the test (ENV)
192
193
  * come first, so a 5xx that also fails an assertion is ENV, not REAL_BUG.
193
194
  */
194
- export const RULES = [envMassFailure, envNetwork, envInfra, envDevice, testDataCollision, envServerError, locatorDriftStrict, timingIntercepted, timingStaleElement, timingRetryPass, locatorDrift, timingFlaky, timingIntermittent, timingKnownFlaky, realBugAssertion];
195
+ export const RULES = [envMissingDependency, envMassFailure, envNetwork, envInfra, envDevice, testDataCollision, envServerError, locatorDriftStrict, timingIntercepted, timingStaleElement, timingRetryPass, locatorDrift, timingFlaky, timingIntermittent, timingKnownFlaky, realBugAssertion];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.17.3",
3
+ "version": "0.18.0",
4
4
  "description": "Tewip finds why each failing test failed - real bug, flaky test, environment, test data or changed selector - for any stack, from the command line, CI, or your editor.",
5
5
  "keywords": [
6
6
  "test-failures",