ai-test-guardrails 0.2.0 → 0.2.1

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.
Files changed (48) hide show
  1. package/README.md +61 -2
  2. package/package.json +1 -1
  3. package/.cursor/mcp.json +0 -8
  4. package/.prettierrc +0 -8
  5. package/eslint.config.js +0 -25
  6. package/examples/playwright/.auth/care-admin-api-rest-dev-0.json +0 -1
  7. package/examples/playwright/.auth/care-admin-api-rest-stage-0.json +0 -1
  8. package/examples/playwright/.auth/care-admin-api-soap-dev-0.json +0 -1
  9. package/examples/playwright/.auth/care-admin-api-soap-stage-0.json +0 -1
  10. package/examples/playwright/.auth/care-admin-dev-0.json +0 -1
  11. package/examples/playwright/.auth/care-admin-stage-0-data.json +0 -1
  12. package/examples/playwright/.auth/care-admin-stage-0.json +0 -1
  13. package/examples/playwright/.auth/care-admin-stage-1.json +0 -1
  14. package/examples/playwright/.auth/care-admin-stage-2.json +0 -1
  15. package/examples/playwright/.auth/care-community-dev-0-data.json +0 -3
  16. package/examples/playwright/.auth/care-community-dev-0.json +0 -15
  17. package/examples/playwright/.auth/care-community-stage-0.json +0 -1
  18. package/examples/playwright/care-admin-base.ts +0 -66
  19. package/examples/playwright/care-community-base.ts +0 -65
  20. package/examples/playwright/constants.ts +0 -3
  21. package/examples/playwright/pages/better-care/better-care-button.page.ts +0 -15
  22. package/examples/playwright/pages/better-care/menu.page.ts +0 -14
  23. package/examples/playwright/pages/care-admin/main.frame.ts +0 -17
  24. package/examples/playwright/pages/care-admin/nav.frame.ts +0 -9
  25. package/examples/playwright/pages/care-admin/setup.page.ts +0 -131
  26. package/examples/playwright/pages/care-admin/study-program.types.ts +0 -339
  27. package/examples/playwright/pages/care-admin/study-programs.page.ts +0 -213
  28. package/examples/playwright/pages/care-admin/toolbar.frame.ts +0 -42
  29. package/examples/playwright/pages/care-community/course-registration.page.ts +0 -43
  30. package/examples/playwright/pages/care-community/documents.page.ts +0 -82
  31. package/examples/playwright/pages/care-community/examination-results.page.ts +0 -58
  32. package/examples/playwright/pages/care-community/nav-bar.page.ts +0 -177
  33. package/examples/playwright/pages/language.page.ts +0 -111
  34. package/examples/playwright/pages/language.types.ts +0 -13
  35. package/examples/playwright/snapshots/care-admin/study-program-search-results.yml +0 -5
  36. package/examples/playwright.config.ts +0 -180
  37. package/src/config/defaultRules.ts +0 -53
  38. package/src/server.ts +0 -274
  39. package/src/types/guardrail.types.ts +0 -121
  40. package/src/utils/astParser.ts +0 -167
  41. package/src/utils/enforcement.ts +0 -123
  42. package/src/utils/frameworkDetector.ts +0 -52
  43. package/src/utils/projectScanner.ts +0 -211
  44. package/src/validators/architecture.ts +0 -131
  45. package/src/validators/determinism.ts +0 -205
  46. package/src/validators/flakeRisk.ts +0 -118
  47. package/tsconfig.json +0 -26
  48. package/vitest.config.ts +0 -8
package/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # ai-test-guardrails
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/ai-test-guardrails.svg)](https://www.npmjs.com/package/ai-test-guardrails)
4
+ [![licence](https://img.shields.io/npm/l/ai-test-guardrails.svg)](./LICENSE)
5
+
3
6
  An MCP (Model Context Protocol) server that provides deterministic guardrails for AI-generated and existing test automation. It validates Playwright and Cypress test proposals using AST-based analysis — detecting flake-prone patterns, enforcing architecture rules, scoring risk, and scanning entire projects in a single call.
4
7
 
5
8
  ## What It Does
6
9
 
7
- - **Validates** AI-generated test code for determinism, flake risk, and architecture compliance
10
+ - **Validates** AI-generated test/ existing test code for determinism, flake risk, and architecture compliance
8
11
  - **Classifies** violations as `critical`, `major`, or `minor` for prioritised remediation
9
12
  - **Scans** entire project directories, validating every test file in one pass with severity breakdown
10
13
  - **Detects** flake-prone constructs: hard sleeps, unbounded retries, unmocked network calls, dynamic selectors
@@ -422,10 +425,66 @@ tests/
422
425
  └── severity.test.ts
423
426
  ```
424
427
 
428
+ ## Contributing
429
+
430
+ Contributions, bug reports, and feature suggestions are welcome. This is an open source project and community input directly shapes the roadmap.
431
+
432
+ ### Reporting Issues
433
+
434
+ If you find a bug or unexpected behaviour, please [open an issue](https://github.com/jch1887/ai-test-guardrails/issues) and include:
435
+
436
+ - A minimal code snippet that reproduces the problem
437
+ - The framework (`playwright` or `cypress`) and enforcement mode you were using
438
+ - The full tool output (JSON response)
439
+ - Your Node.js version (`node --version`)
440
+
441
+ ### Suggesting Features or New Rules
442
+
443
+ Have an idea for a new validation rule, flake risk factor, or tool feature? [Open an issue](https://github.com/jch1887/ai-test-guardrails/issues) with the label **`enhancement`** and describe:
444
+
445
+ - The problem or pattern you want to catch
446
+ - Why it matters for test reliability or architecture
447
+ - Any example code that should trigger (or not trigger) the rule
448
+
449
+ ### Submitting a Pull Request
450
+
451
+ 1. Fork the repository and create a branch from `main`
452
+ 2. Install dependencies: `npm install`
453
+ 3. Make your changes — new rules live in `src/validators/`, new flake factors in `src/validators/flakeRisk.ts`
454
+ 4. Add or update tests in `tests/` to cover your change
455
+ 5. Ensure all checks pass:
456
+
457
+ ```bash
458
+ npm test # all tests must pass
459
+ npm run lint # no lint errors
460
+ npm run typecheck # no type errors
461
+ ```
462
+
463
+ 6. Open a pull request against `main` with a clear description of what changed and why
464
+
465
+ ### Development Setup
466
+
467
+ ```bash
468
+ git clone https://github.com/jch1887/ai-test-guardrails.git
469
+ cd ai-test-guardrails
470
+ npm install
471
+ npm run build
472
+ npm test
473
+ ```
474
+
475
+ ### Code Style
476
+
477
+ - TypeScript strict mode is enabled — avoid `any`
478
+ - Prettier and ESLint configs are included; run `npm run format` before committing
479
+ - Keep rule logic self-contained and unit-testable
480
+ - Violation messages should be actionable: say what to do, not just what went wrong
481
+
482
+ ---
483
+
425
484
  ## Roadmap
426
485
 
427
486
  - [x] **v0.1** — Core validation engine, three enforcement modes, project scanning
428
- - [x] **v0.2** — Violation severity tiers (critical / major / minor), k6 detection, severity breakdown in scan summary, two-pass `.js`/`.ts` file discovery
487
+ - [x] **v0.2** — Violation severity tiers (critical / major / minor), severity breakdown in scan summary, two-pass `.js`/`.ts` file discovery
429
488
  - [ ] **v0.3** — Auto-fix suggestions in violation messages
430
489
  - [ ] **v0.3** — Cypress-specific selector pattern detection
431
490
  - [ ] **v0.4** — Support for custom rule plugins
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-test-guardrails",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "MCP server providing deterministic guardrails for AI-generated test automation",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",
package/.cursor/mcp.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "ai-test-guardrails": {
4
- "command": "node",
5
- "args": ["/Users/jason/Documents/GitHub/ai-test-guardrails/dist/server.js"]
6
- }
7
- }
8
- }
package/.prettierrc DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "semi": true,
3
- "trailingComma": "all",
4
- "singleQuote": false,
5
- "printWidth": 100,
6
- "tabWidth": 2,
7
- "arrowParens": "always"
8
- }
package/eslint.config.js DELETED
@@ -1,25 +0,0 @@
1
- import tseslint from "typescript-eslint";
2
-
3
- export default tseslint.config(
4
- {
5
- ignores: ["dist/", "node_modules/", "*.config.*"],
6
- },
7
- ...tseslint.configs.strictTypeChecked,
8
- {
9
- languageOptions: {
10
- parserOptions: {
11
- projectService: true,
12
- tsconfigRootDir: import.meta.dirname,
13
- },
14
- },
15
- rules: {
16
- "@typescript-eslint/no-unused-vars": [
17
- "error",
18
- { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
19
- ],
20
- "@typescript-eslint/explicit-function-return-type": "error",
21
- "@typescript-eslint/no-explicit-any": "error",
22
- "@typescript-eslint/strict-boolean-expressions": "off",
23
- },
24
- },
25
- );
@@ -1,3 +0,0 @@
1
- {
2
- "localStorage": {}
3
- }
@@ -1,15 +0,0 @@
1
- {
2
- "cookies": [
3
- {
4
- "name": "PHPSESSID",
5
- "value": "3qoqnrnr3rj3fola066u63pgm2",
6
- "domain": "fs.care.idss-dev.iu-aws.de",
7
- "path": "/",
8
- "expires": -1,
9
- "httpOnly": true,
10
- "secure": false,
11
- "sameSite": "Lax"
12
- }
13
- ],
14
- "origins": []
15
- }
@@ -1,66 +0,0 @@
1
- import { createTest, expect } from "@iu/playwright-test-utils/care-admin/base";
2
- import type { Page } from "@playwright/test";
3
- import { BetterCareButtonPage } from "./pages/better-care/better-care-button.page";
4
- import { MenuPage } from "./pages/better-care/menu.page";
5
- import { Main } from "./pages/care-admin/main.frame";
6
- import { Nav } from "./pages/care-admin/nav.frame";
7
- import { SetupPage } from "./pages/care-admin/setup.page";
8
- import { StudyProgramsPage } from "./pages/care-admin/study-programs.page";
9
- import { Toolbar } from "./pages/care-admin/toolbar.frame";
10
-
11
- /*
12
- This file allows us to inherit any fixtures from the utils library as well as add our own.
13
-
14
- See https://playwright.dev/docs/test-fixtures for more details or the Care Community base file for a more comprehensive example.
15
- */
16
-
17
- type TestFixtures = {
18
- page: Page;
19
- setup: SetupPage;
20
- studyPrograms: StudyProgramsPage;
21
- toolbar: Toolbar;
22
- main: Main;
23
- nav: Nav;
24
- // Better Care
25
- menu: MenuPage;
26
- betterCareButton: BetterCareButtonPage;
27
- };
28
-
29
- const base = createTest();
30
-
31
- const test = base.extend<TestFixtures>({
32
- async page({ page }, use) {
33
- await use(page);
34
- },
35
-
36
- async setup({ page }, use) {
37
- await use(new SetupPage(page));
38
- },
39
-
40
- async toolbar({ page }, use) {
41
- await use(new Toolbar(page));
42
- },
43
-
44
- async nav({ page }, use) {
45
- await use(new Nav(page));
46
- },
47
-
48
- async main({ page }, use) {
49
- await use(new Main(page));
50
- },
51
-
52
- async studyPrograms({ page, setup, toolbar, main }, use) {
53
- await use(new StudyProgramsPage(page, setup, toolbar, main));
54
- },
55
-
56
- // Better Care
57
- async menu({ nav }, use) {
58
- await use(new MenuPage(nav));
59
- },
60
-
61
- async betterCareButton({ page }, use) {
62
- await use(new BetterCareButtonPage(page));
63
- },
64
- });
65
-
66
- export { test, expect };
@@ -1,65 +0,0 @@
1
- import {
2
- createTest,
3
- expect,
4
- } from "@iu/playwright-test-utils/care-community/base";
5
- import type { Page } from "@playwright/test";
6
- import { CourseRegistrationPage } from "./pages/care-community/course-registration.page";
7
- import { DocumentsPage } from "./pages/care-community/documents.page";
8
- import { ExaminationResultsPage } from "./pages/care-community/examination-results.page";
9
- import { NavBar } from "./pages/care-community/nav-bar.page";
10
- import { Language } from "./pages/language.page";
11
-
12
- const base = createTest();
13
-
14
- type TestFixtures = {
15
- page: Page;
16
- navBar: NavBar;
17
- courseRegistrationPage: CourseRegistrationPage;
18
- documentsPage: DocumentsPage;
19
- examinationResultsPage: ExaminationResultsPage;
20
- language: Language;
21
- };
22
-
23
- /*
24
- This file allows us to inherit any fixtures from the utils library as well as add our own.
25
-
26
- In this example we are inheriting the fixtures provided from the utils library and adding our own additional fixtures.
27
-
28
- For example we have added `language` and `navBar` which means they can be imported into all our spec files without manual imports.
29
-
30
- See https://playwright.dev/docs/test-fixtures for more details.
31
- */
32
-
33
- const test = base.extend<TestFixtures>({
34
- async page({ page }, use) {
35
- await use(page);
36
- },
37
-
38
- async language({ page }, use, testInfo) {
39
- const language = new Language(page, testInfo);
40
-
41
- await use(language);
42
- },
43
-
44
- async navBar({ page, language }, use) {
45
- const navBar = new NavBar(page, language);
46
- await use(navBar);
47
- },
48
-
49
- async documentsPage({ page, language }, use) {
50
- const documentsPage = new DocumentsPage(page, language);
51
- await use(documentsPage);
52
- },
53
-
54
- async examinationResultsPage({ page }, use) {
55
- const examinationResultsPage = new ExaminationResultsPage(page);
56
- await use(examinationResultsPage);
57
- },
58
-
59
- async courseRegistrationPage({ page }, use) {
60
- const courseRegistrationPage = new CourseRegistrationPage(page);
61
- await use(courseRegistrationPage);
62
- },
63
- });
64
-
65
- export { test, expect };
@@ -1,3 +0,0 @@
1
- export const PROFILE_ID = "10608473"; // Academy5Id/AC5ID - QI Esmeralda
2
- export const BOOKING_ID = "10958750"; // Default booking for "QI Esmeralda FS Study Tester"
3
- export const COURSE_ID = "10050031"; // Course ID for "Einführung in die Soziale Arbeit (DLBSAESA01-QI)" in FS BA QIBA test study programme
@@ -1,15 +0,0 @@
1
- import type { Locator, Page } from "@playwright/test";
2
-
3
- export class BetterCareButtonPage {
4
- readonly bubble: Locator;
5
- readonly infoPanel: Locator;
6
- readonly debugModeToggle: Locator;
7
-
8
- constructor(private readonly page: Page) {
9
- this.bubble = this.page
10
- .getByTestId("bcv2-global-module-container")
11
- .filter({ hasText: "Better Care" });
12
- this.infoPanel = this.page.getByTestId("bcv2-info-panel");
13
- this.debugModeToggle = this.infoPanel.getByTestId("debug-mode-toggle");
14
- }
15
- }
@@ -1,14 +0,0 @@
1
- import type { Locator } from "@playwright/test";
2
- import type { Nav } from "../care-admin/nav.frame";
3
-
4
- export class MenuPage {
5
- readonly peopleSearch: Locator;
6
- readonly studyPrograms: Locator;
7
- readonly showFullMenu: Locator;
8
-
9
- constructor(private readonly nav: Nav) {
10
- this.peopleSearch = this.nav.frame.getByTestId("menu-people-search");
11
- this.studyPrograms = this.nav.frame.getByTestId("menu-study-programs");
12
- this.showFullMenu = this.nav.frame.getByTestId("menu-show-full-menu");
13
- }
14
- }
@@ -1,17 +0,0 @@
1
- import type { FrameLocator, Locator, Page } from "@playwright/test";
2
-
3
- type MenuItems = {
4
- edit: Locator;
5
- };
6
-
7
- export class Main {
8
- readonly frame: FrameLocator;
9
- readonly menuItems: MenuItems;
10
-
11
- constructor(private readonly page: Page) {
12
- this.frame = this.page.frameLocator("#main");
13
- this.menuItems = {
14
- edit: this.frame.locator(".menuitems").filter({ hasText: "Bearbeiten" }),
15
- };
16
- }
17
- }
@@ -1,9 +0,0 @@
1
- import type { FrameLocator, Page } from "@playwright/test";
2
-
3
- export class Nav {
4
- readonly frame: FrameLocator;
5
-
6
- constructor(private readonly page: Page) {
7
- this.frame = this.page.frameLocator("#nav");
8
- }
9
- }
@@ -1,131 +0,0 @@
1
- import type { Frame, FrameLocator, Locator, Page } from "@playwright/test";
2
- import { expect } from "playwright/care-admin-base";
3
-
4
- export class SetupPage {
5
- readonly sideBySideFrame: FrameLocator;
6
- readonly navBar: Locator;
7
- readonly reminder: Locator;
8
- readonly reminderClose: Locator;
9
-
10
- constructor(private readonly page: Page) {
11
- this.sideBySideFrame = this.page.frameLocator("#side-by-side-iframe");
12
- this.navBar = this.page.locator("#navbar-academy-five");
13
- this.reminder = this.page.locator(".p-reminder");
14
- this.reminderClose = this.page.locator(".p-reminder-close");
15
- }
16
-
17
- private async waitForHomeUrl() {
18
- // wait till the url changed to /home (note that Firefox loads home#)
19
- await this.page.waitForURL("/home**");
20
- }
21
-
22
- private async setupAuthenticatedPage() {
23
- // Navigate to login page
24
- await this.page.goto("/");
25
-
26
- await this.waitForHomeUrl();
27
-
28
- // wait till #navbar-academy-five exists and is visible
29
- await expect(this.navBar).toBeVisible({ timeout: 20000 });
30
-
31
- // wait till everything is loaded
32
- await this.page.waitForLoadState("domcontentloaded");
33
-
34
- // wait till frame elements exist on page
35
- await this.page.waitForSelector("#nav", { state: "visible" });
36
- await this.page.waitForSelector("#main", { state: "visible" });
37
-
38
- // Debug: Log all available frames
39
- const allFrames: Record<string, Frame> = {};
40
- for (const f of this.page.frames()) {
41
- if (f.isDetached()) {
42
- continue;
43
- }
44
- allFrames[
45
- f.name() ||
46
- (await f
47
- .frameElement()
48
- .then((el) => el?.getAttribute("id"))
49
- .catch(() => null)) ||
50
- ""
51
- ] = f;
52
- }
53
-
54
- // get frame accessors
55
- const navFrame = allFrames.nav;
56
- const mainFrame = allFrames.main;
57
- const toolbarFrame = allFrames.toolbar;
58
- const sideBySideFrame = allFrames["side-by-side-iframe"];
59
-
60
- // check if all frames exist
61
- if (!navFrame) {
62
- console.error("Failed to get navigation frame [name=nav]", navFrame);
63
- throw new Error("Failed to get navigation frame [name=nav]");
64
- }
65
- if (!mainFrame) {
66
- console.error("Failed to get main frame [name=main]", mainFrame);
67
- throw new Error("Failed to get main frame [name=main]");
68
- }
69
- if (!toolbarFrame) {
70
- console.error("Failed to get toolbar frame [name=toolbar]", toolbarFrame);
71
- throw new Error("Failed to get toolbar frame [name=toolbar]");
72
- }
73
- if (!sideBySideFrame) {
74
- throw new Error(
75
- "Failed to get side-by-side-iframe [name=side-by-side-iframe]",
76
- );
77
- }
78
-
79
- // wait for all frames to be loaded
80
- await Promise.all([
81
- navFrame.waitForLoadState("domcontentloaded"),
82
- mainFrame.waitForLoadState("domcontentloaded"),
83
- toolbarFrame.waitForLoadState("domcontentloaded"),
84
- ]);
85
-
86
- // wait untl mainFrame has url "/dashboard"
87
- await mainFrame.waitForURL("/dashboard");
88
- }
89
-
90
- public async frameGoto(url: string, timeout?: number) {
91
- await this.page.evaluate((url) => {
92
- const iFrame = document.querySelector<HTMLIFrameElement>(
93
- 'iframe[name="main"]',
94
- );
95
- if (iFrame) {
96
- iFrame.src = url;
97
- }
98
- }, url);
99
-
100
- // Wait for frame to be fully loaded
101
- await this.page.waitForLoadState("domcontentloaded");
102
- await this.page.waitForLoadState("networkidle", { timeout });
103
- }
104
-
105
- public async loadPage(caseName?: string) {
106
- await this.page.route("**/*", (route) => {
107
- const headers = {
108
- ...route.request().headers(),
109
- "X-Test-Context": caseName || "default",
110
- };
111
- route.continue({ headers });
112
- });
113
-
114
- await this.setupAuthenticatedPage();
115
- await this.waitForHomeUrl();
116
-
117
- if (await this.reminder.isVisible()) {
118
- await this.reminderClose.click();
119
- }
120
-
121
- return {
122
- registerApiMock: async (
123
- url: string,
124
- response: {
125
- statusCode: number;
126
- body: string;
127
- },
128
- ) => {},
129
- };
130
- }
131
- }