@avi770/testteam 1.2.0 → 2.0.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.
- package/CHANGELOG.md +29 -1
- package/README.md +53 -6
- package/agents/24-signup-onboarding-tester.ts +429 -0
- package/agents/25-crud-flow-tester.ts +302 -0
- package/agents/26-form-validator.ts +297 -0
- package/agents/27-search-filter-tester.ts +326 -0
- package/agents/28-navigation-routing-tester.ts +425 -0
- package/agents/29-responsive-interaction-tester.ts +350 -0
- package/agents/30-multi-user-scenario-tester.ts +319 -0
- package/agents/31-load-tester.ts +134 -0
- package/agents/32-memory-leak-detector.ts +194 -0
- package/agents/33-bundle-analyzer.ts +132 -0
- package/agents/34-xss-scanner.ts +191 -0
- package/agents/35-csrf-tester.ts +82 -0
- package/agents/36-auth-fuzzer.ts +194 -0
- package/agents/37-dependency-scanner.ts +176 -0
- package/agents/38-secrets-scanner.ts +137 -0
- package/agents/39-api-contract-tester.ts +199 -0
- package/agents/40-rate-limit-tester.ts +94 -0
- package/agents/41-api-pagination-tester.ts +97 -0
- package/agents/42-graphql-tester.ts +222 -0
- package/agents/43-data-consistency-checker.ts +205 -0
- package/agents/44-backup-recovery-tester.ts +152 -0
- package/agents/45-data-privacy-scanner.ts +125 -0
- package/agents/46-seo-auditor.ts +294 -0
- package/agents/47-social-preview-tester.ts +232 -0
- package/agents/48-lighthouse-auditor.ts +213 -0
- package/agents/49-i18n-tester.ts +198 -0
- package/agents/50-timezone-tester.ts +173 -0
- package/agents/51-error-recovery-tester.ts +155 -0
- package/agents/52-offline-mode-tester.ts +180 -0
- package/agents/53-graceful-degradation-tester.ts +156 -0
- package/agents/54-websocket-tester.ts +151 -0
- package/agents/55-realtime-sync-tester.ts +194 -0
- package/agents/56-file-upload-tester.ts +194 -0
- package/agents/57-export-tester.ts +174 -0
- package/agents/58-payment-flow-tester.ts +183 -0
- package/agents/59-ssl-tls-auditor.ts +141 -0
- package/agents/60-dns-cdn-tester.ts +117 -0
- package/agents/61-docker-health-checker.ts +111 -0
- package/agents/62-env-config-validator.ts +152 -0
- package/agents/63-log-quality-auditor.ts +136 -0
- package/agents/64-analytics-tracker-tester.ts +165 -0
- package/agents/65-gdpr-compliance-tester.ts +215 -0
- package/agents/66-soc2-control-validator.ts +210 -0
- package/agents/67-wcag-aaa-tester.ts +241 -0
- package/agents/68-dead-code-detector.ts +135 -0
- package/agents/69-type-safety-auditor.ts +164 -0
- package/agents/70-complexity-analyzer.ts +179 -0
- package/agents/__tests__/24-signup-onboarding-tester.test.ts +274 -0
- package/agents/__tests__/25-crud-flow-tester.test.ts +322 -0
- package/agents/__tests__/26-form-validator.test.ts +345 -0
- package/agents/__tests__/27-search-filter-tester.test.ts +311 -0
- package/agents/__tests__/28-navigation-routing-tester.test.ts +328 -0
- package/agents/__tests__/29-responsive-interaction-tester.test.ts +297 -0
- package/agents/__tests__/30-multi-user-scenario-tester.test.ts +328 -0
- package/agents/__tests__/31-load-tester.test.ts +189 -0
- package/agents/__tests__/32-memory-leak-detector.test.ts +251 -0
- package/agents/__tests__/33-bundle-analyzer.test.ts +237 -0
- package/agents/__tests__/34-xss-scanner.test.ts +258 -0
- package/agents/__tests__/35-csrf-tester.test.ts +200 -0
- package/agents/__tests__/36-auth-fuzzer.test.ts +214 -0
- package/agents/__tests__/37-dependency-scanner.test.ts +266 -0
- package/agents/__tests__/38-secrets-scanner.test.ts +224 -0
- package/agents/__tests__/39-api-contract-tester.test.ts +312 -0
- package/agents/__tests__/40-rate-limit-tester.test.ts +192 -0
- package/agents/__tests__/41-api-pagination-tester.test.ts +198 -0
- package/agents/__tests__/42-graphql-tester.test.ts +252 -0
- package/agents/__tests__/43-data-consistency-checker.test.ts +232 -0
- package/agents/__tests__/44-backup-recovery-tester.test.ts +222 -0
- package/agents/__tests__/45-data-privacy-scanner.test.ts +223 -0
- package/agents/__tests__/46-seo-auditor.test.ts +261 -0
- package/agents/__tests__/47-social-preview-tester.test.ts +245 -0
- package/agents/__tests__/48-lighthouse-auditor.test.ts +276 -0
- package/agents/__tests__/49-i18n-tester.test.ts +201 -0
- package/agents/__tests__/50-timezone-tester.test.ts +172 -0
- package/agents/__tests__/51-error-recovery-tester.test.ts +162 -0
- package/agents/__tests__/52-offline-mode-tester.test.ts +164 -0
- package/agents/__tests__/53-graceful-degradation-tester.test.ts +168 -0
- package/agents/__tests__/54-websocket-tester.test.ts +157 -0
- package/agents/__tests__/55-realtime-sync-tester.test.ts +181 -0
- package/agents/__tests__/56-file-upload-tester.test.ts +172 -0
- package/agents/__tests__/57-export-tester.test.ts +169 -0
- package/agents/__tests__/58-payment-flow-tester.test.ts +182 -0
- package/agents/__tests__/59-ssl-tls-auditor.test.ts +179 -0
- package/agents/__tests__/60-dns-cdn-tester.test.ts +176 -0
- package/agents/__tests__/61-docker-health-checker.test.ts +150 -0
- package/agents/__tests__/62-env-config-validator.test.ts +166 -0
- package/agents/__tests__/63-log-quality-auditor.test.ts +175 -0
- package/agents/__tests__/64-analytics-tracker-tester.test.ts +158 -0
- package/agents/__tests__/65-gdpr-compliance-tester.test.ts +174 -0
- package/agents/__tests__/66-soc2-control-validator.test.ts +183 -0
- package/agents/__tests__/67-wcag-aaa-tester.test.ts +190 -0
- package/agents/__tests__/68-dead-code-detector.test.ts +174 -0
- package/agents/__tests__/69-type-safety-auditor.test.ts +173 -0
- package/agents/__tests__/70-complexity-analyzer.test.ts +177 -0
- package/agents/__tests__/registry.test.ts +13 -13
- package/agents/registry.ts +146 -5
- package/core/__tests__/integration.test.ts +4 -4
- package/core/__tests__/orchestrator.test.ts +17 -16
- package/core/license.ts +208 -211
- package/core/orchestrator.ts +6 -4
- package/dist/agents/24-signup-onboarding-tester.d.ts +35 -0
- package/dist/agents/24-signup-onboarding-tester.d.ts.map +1 -0
- package/dist/agents/24-signup-onboarding-tester.js +357 -0
- package/dist/agents/24-signup-onboarding-tester.js.map +1 -0
- package/dist/agents/25-crud-flow-tester.d.ts +11 -0
- package/dist/agents/25-crud-flow-tester.d.ts.map +1 -0
- package/dist/agents/25-crud-flow-tester.js +253 -0
- package/dist/agents/25-crud-flow-tester.js.map +1 -0
- package/dist/agents/26-form-validator.d.ts +12 -0
- package/dist/agents/26-form-validator.d.ts.map +1 -0
- package/dist/agents/26-form-validator.js +257 -0
- package/dist/agents/26-form-validator.js.map +1 -0
- package/dist/agents/27-search-filter-tester.d.ts +20 -0
- package/dist/agents/27-search-filter-tester.d.ts.map +1 -0
- package/dist/agents/27-search-filter-tester.js +267 -0
- package/dist/agents/27-search-filter-tester.js.map +1 -0
- package/dist/agents/28-navigation-routing-tester.d.ts +32 -0
- package/dist/agents/28-navigation-routing-tester.d.ts.map +1 -0
- package/dist/agents/28-navigation-routing-tester.js +363 -0
- package/dist/agents/28-navigation-routing-tester.js.map +1 -0
- package/dist/agents/29-responsive-interaction-tester.d.ts +26 -0
- package/dist/agents/29-responsive-interaction-tester.d.ts.map +1 -0
- package/dist/agents/29-responsive-interaction-tester.js +272 -0
- package/dist/agents/29-responsive-interaction-tester.js.map +1 -0
- package/dist/agents/30-multi-user-scenario-tester.d.ts +24 -0
- package/dist/agents/30-multi-user-scenario-tester.d.ts.map +1 -0
- package/dist/agents/30-multi-user-scenario-tester.js +254 -0
- package/dist/agents/30-multi-user-scenario-tester.js.map +1 -0
- package/dist/agents/31-load-tester.d.ts +12 -0
- package/dist/agents/31-load-tester.d.ts.map +1 -0
- package/dist/agents/31-load-tester.js +110 -0
- package/dist/agents/31-load-tester.js.map +1 -0
- package/dist/agents/32-memory-leak-detector.d.ts +12 -0
- package/dist/agents/32-memory-leak-detector.d.ts.map +1 -0
- package/dist/agents/32-memory-leak-detector.js +167 -0
- package/dist/agents/32-memory-leak-detector.js.map +1 -0
- package/dist/agents/33-bundle-analyzer.d.ts +10 -0
- package/dist/agents/33-bundle-analyzer.d.ts.map +1 -0
- package/dist/agents/33-bundle-analyzer.js +111 -0
- package/dist/agents/33-bundle-analyzer.js.map +1 -0
- package/dist/agents/34-xss-scanner.d.ts +11 -0
- package/dist/agents/34-xss-scanner.d.ts.map +1 -0
- package/dist/agents/34-xss-scanner.js +164 -0
- package/dist/agents/34-xss-scanner.js.map +1 -0
- package/dist/agents/35-csrf-tester.d.ts +11 -0
- package/dist/agents/35-csrf-tester.d.ts.map +1 -0
- package/dist/agents/35-csrf-tester.js +70 -0
- package/dist/agents/35-csrf-tester.js.map +1 -0
- package/dist/agents/36-auth-fuzzer.d.ts +13 -0
- package/dist/agents/36-auth-fuzzer.d.ts.map +1 -0
- package/dist/agents/36-auth-fuzzer.js +163 -0
- package/dist/agents/36-auth-fuzzer.js.map +1 -0
- package/dist/agents/37-dependency-scanner.d.ts +11 -0
- package/dist/agents/37-dependency-scanner.d.ts.map +1 -0
- package/dist/agents/37-dependency-scanner.js +139 -0
- package/dist/agents/37-dependency-scanner.js.map +1 -0
- package/dist/agents/38-secrets-scanner.d.ts +11 -0
- package/dist/agents/38-secrets-scanner.d.ts.map +1 -0
- package/dist/agents/38-secrets-scanner.js +116 -0
- package/dist/agents/38-secrets-scanner.js.map +1 -0
- package/dist/agents/39-api-contract-tester.d.ts +12 -0
- package/dist/agents/39-api-contract-tester.d.ts.map +1 -0
- package/dist/agents/39-api-contract-tester.js +142 -0
- package/dist/agents/39-api-contract-tester.js.map +1 -0
- package/dist/agents/40-rate-limit-tester.d.ts +12 -0
- package/dist/agents/40-rate-limit-tester.d.ts.map +1 -0
- package/dist/agents/40-rate-limit-tester.js +79 -0
- package/dist/agents/40-rate-limit-tester.js.map +1 -0
- package/dist/agents/41-api-pagination-tester.d.ts +12 -0
- package/dist/agents/41-api-pagination-tester.d.ts.map +1 -0
- package/dist/agents/41-api-pagination-tester.js +79 -0
- package/dist/agents/41-api-pagination-tester.js.map +1 -0
- package/dist/agents/42-graphql-tester.d.ts +13 -0
- package/dist/agents/42-graphql-tester.d.ts.map +1 -0
- package/dist/agents/42-graphql-tester.js +187 -0
- package/dist/agents/42-graphql-tester.js.map +1 -0
- package/dist/agents/43-data-consistency-checker.d.ts +11 -0
- package/dist/agents/43-data-consistency-checker.d.ts.map +1 -0
- package/dist/agents/43-data-consistency-checker.js +176 -0
- package/dist/agents/43-data-consistency-checker.js.map +1 -0
- package/dist/agents/44-backup-recovery-tester.d.ts +11 -0
- package/dist/agents/44-backup-recovery-tester.d.ts.map +1 -0
- package/dist/agents/44-backup-recovery-tester.js +128 -0
- package/dist/agents/44-backup-recovery-tester.js.map +1 -0
- package/dist/agents/45-data-privacy-scanner.d.ts +11 -0
- package/dist/agents/45-data-privacy-scanner.d.ts.map +1 -0
- package/dist/agents/45-data-privacy-scanner.js +100 -0
- package/dist/agents/45-data-privacy-scanner.js.map +1 -0
- package/dist/agents/46-seo-auditor.d.ts +12 -0
- package/dist/agents/46-seo-auditor.d.ts.map +1 -0
- package/dist/agents/46-seo-auditor.js +275 -0
- package/dist/agents/46-seo-auditor.js.map +1 -0
- package/dist/agents/47-social-preview-tester.d.ts +11 -0
- package/dist/agents/47-social-preview-tester.d.ts.map +1 -0
- package/dist/agents/47-social-preview-tester.js +219 -0
- package/dist/agents/47-social-preview-tester.js.map +1 -0
- package/dist/agents/48-lighthouse-auditor.d.ts +11 -0
- package/dist/agents/48-lighthouse-auditor.d.ts.map +1 -0
- package/dist/agents/48-lighthouse-auditor.js +192 -0
- package/dist/agents/48-lighthouse-auditor.js.map +1 -0
- package/dist/agents/49-i18n-tester.d.ts +13 -0
- package/dist/agents/49-i18n-tester.d.ts.map +1 -0
- package/dist/agents/49-i18n-tester.js +172 -0
- package/dist/agents/49-i18n-tester.js.map +1 -0
- package/dist/agents/50-timezone-tester.d.ts +11 -0
- package/dist/agents/50-timezone-tester.d.ts.map +1 -0
- package/dist/agents/50-timezone-tester.js +152 -0
- package/dist/agents/50-timezone-tester.js.map +1 -0
- package/dist/agents/51-error-recovery-tester.d.ts +11 -0
- package/dist/agents/51-error-recovery-tester.d.ts.map +1 -0
- package/dist/agents/51-error-recovery-tester.js +134 -0
- package/dist/agents/51-error-recovery-tester.js.map +1 -0
- package/dist/agents/52-offline-mode-tester.d.ts +12 -0
- package/dist/agents/52-offline-mode-tester.d.ts.map +1 -0
- package/dist/agents/52-offline-mode-tester.js +161 -0
- package/dist/agents/52-offline-mode-tester.js.map +1 -0
- package/dist/agents/53-graceful-degradation-tester.d.ts +12 -0
- package/dist/agents/53-graceful-degradation-tester.d.ts.map +1 -0
- package/dist/agents/53-graceful-degradation-tester.js +130 -0
- package/dist/agents/53-graceful-degradation-tester.js.map +1 -0
- package/dist/agents/54-websocket-tester.d.ts +10 -0
- package/dist/agents/54-websocket-tester.d.ts.map +1 -0
- package/dist/agents/54-websocket-tester.js +132 -0
- package/dist/agents/54-websocket-tester.js.map +1 -0
- package/dist/agents/55-realtime-sync-tester.d.ts +11 -0
- package/dist/agents/55-realtime-sync-tester.d.ts.map +1 -0
- package/dist/agents/55-realtime-sync-tester.js +175 -0
- package/dist/agents/55-realtime-sync-tester.js.map +1 -0
- package/dist/agents/56-file-upload-tester.d.ts +12 -0
- package/dist/agents/56-file-upload-tester.d.ts.map +1 -0
- package/dist/agents/56-file-upload-tester.js +166 -0
- package/dist/agents/56-file-upload-tester.js.map +1 -0
- package/dist/agents/57-export-tester.d.ts +11 -0
- package/dist/agents/57-export-tester.d.ts.map +1 -0
- package/dist/agents/57-export-tester.js +155 -0
- package/dist/agents/57-export-tester.js.map +1 -0
- package/dist/agents/58-payment-flow-tester.d.ts +11 -0
- package/dist/agents/58-payment-flow-tester.d.ts.map +1 -0
- package/dist/agents/58-payment-flow-tester.js +159 -0
- package/dist/agents/58-payment-flow-tester.js.map +1 -0
- package/dist/agents/59-ssl-tls-auditor.d.ts +10 -0
- package/dist/agents/59-ssl-tls-auditor.d.ts.map +1 -0
- package/dist/agents/59-ssl-tls-auditor.js +132 -0
- package/dist/agents/59-ssl-tls-auditor.js.map +1 -0
- package/dist/agents/60-dns-cdn-tester.d.ts +10 -0
- package/dist/agents/60-dns-cdn-tester.d.ts.map +1 -0
- package/dist/agents/60-dns-cdn-tester.js +105 -0
- package/dist/agents/60-dns-cdn-tester.js.map +1 -0
- package/dist/agents/61-docker-health-checker.d.ts +10 -0
- package/dist/agents/61-docker-health-checker.d.ts.map +1 -0
- package/dist/agents/61-docker-health-checker.js +95 -0
- package/dist/agents/61-docker-health-checker.js.map +1 -0
- package/dist/agents/62-env-config-validator.d.ts +10 -0
- package/dist/agents/62-env-config-validator.d.ts.map +1 -0
- package/dist/agents/62-env-config-validator.js +132 -0
- package/dist/agents/62-env-config-validator.js.map +1 -0
- package/dist/agents/63-log-quality-auditor.d.ts +9 -0
- package/dist/agents/63-log-quality-auditor.d.ts.map +1 -0
- package/dist/agents/63-log-quality-auditor.js +121 -0
- package/dist/agents/63-log-quality-auditor.js.map +1 -0
- package/dist/agents/64-analytics-tracker-tester.d.ts +10 -0
- package/dist/agents/64-analytics-tracker-tester.d.ts.map +1 -0
- package/dist/agents/64-analytics-tracker-tester.js +146 -0
- package/dist/agents/64-analytics-tracker-tester.js.map +1 -0
- package/dist/agents/65-gdpr-compliance-tester.d.ts +13 -0
- package/dist/agents/65-gdpr-compliance-tester.d.ts.map +1 -0
- package/dist/agents/65-gdpr-compliance-tester.js +186 -0
- package/dist/agents/65-gdpr-compliance-tester.js.map +1 -0
- package/dist/agents/66-soc2-control-validator.d.ts +14 -0
- package/dist/agents/66-soc2-control-validator.d.ts.map +1 -0
- package/dist/agents/66-soc2-control-validator.js +178 -0
- package/dist/agents/66-soc2-control-validator.js.map +1 -0
- package/dist/agents/67-wcag-aaa-tester.d.ts +13 -0
- package/dist/agents/67-wcag-aaa-tester.d.ts.map +1 -0
- package/dist/agents/67-wcag-aaa-tester.js +207 -0
- package/dist/agents/67-wcag-aaa-tester.js.map +1 -0
- package/dist/agents/68-dead-code-detector.d.ts +9 -0
- package/dist/agents/68-dead-code-detector.d.ts.map +1 -0
- package/dist/agents/68-dead-code-detector.js +116 -0
- package/dist/agents/68-dead-code-detector.js.map +1 -0
- package/dist/agents/69-type-safety-auditor.d.ts +9 -0
- package/dist/agents/69-type-safety-auditor.d.ts.map +1 -0
- package/dist/agents/69-type-safety-auditor.js +148 -0
- package/dist/agents/69-type-safety-auditor.js.map +1 -0
- package/dist/agents/70-complexity-analyzer.d.ts +10 -0
- package/dist/agents/70-complexity-analyzer.d.ts.map +1 -0
- package/dist/agents/70-complexity-analyzer.js +154 -0
- package/dist/agents/70-complexity-analyzer.js.map +1 -0
- package/dist/agents/registry.d.ts +3 -3
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +146 -5
- package/dist/agents/registry.js.map +1 -1
- package/dist/core/license.js +2 -5
- package/dist/core/license.js.map +1 -1
- package/dist/core/orchestrator.d.ts.map +1 -1
- package/dist/core/orchestrator.js +6 -4
- package/dist/core/orchestrator.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Phase } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
name: 'test',
|
|
13
|
+
auth: {
|
|
14
|
+
loginUrl: 'http://localhost/login',
|
|
15
|
+
credentials: {
|
|
16
|
+
admin: { email: 'admin@test.com', password: 'secret' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
modules: [
|
|
20
|
+
{ id: 'dashboard', route: '/dashboard', sidebarIcon: true },
|
|
21
|
+
{ id: 'tasks', route: '/tasks', sidebarIcon: true },
|
|
22
|
+
],
|
|
23
|
+
environments: {
|
|
24
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
25
|
+
},
|
|
26
|
+
portals: [],
|
|
27
|
+
breakpoints: [],
|
|
28
|
+
integrations: [],
|
|
29
|
+
workers: [],
|
|
30
|
+
workflows: [],
|
|
31
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
32
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
33
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
34
|
+
agentClassification: { blocking: [], advisory: [25] },
|
|
35
|
+
thresholds: {
|
|
36
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
37
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
38
|
+
apiResponseTime: 2000,
|
|
39
|
+
bundleSizeLimit: 500000,
|
|
40
|
+
maxConsoleErrors: 0,
|
|
41
|
+
},
|
|
42
|
+
disabledAgents: [],
|
|
43
|
+
disabledAgentSet: new Set<number>(),
|
|
44
|
+
...overrides,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Mock fetch (preFlight URL reachability)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
const fetchMock = vi.fn();
|
|
53
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Mock helpers
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
60
|
+
|
|
61
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
62
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
63
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
64
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
65
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Mock Playwright
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
interface MockLocator {
|
|
73
|
+
count: ReturnType<typeof vi.fn>;
|
|
74
|
+
first: ReturnType<typeof vi.fn>;
|
|
75
|
+
nth: ReturnType<typeof vi.fn>;
|
|
76
|
+
click: ReturnType<typeof vi.fn>;
|
|
77
|
+
fill: ReturnType<typeof vi.fn>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function makeMockLocator(countVal = 0): MockLocator {
|
|
81
|
+
const locator: MockLocator = {
|
|
82
|
+
count: vi.fn().mockResolvedValue(countVal),
|
|
83
|
+
first: vi.fn(),
|
|
84
|
+
nth: vi.fn(),
|
|
85
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
86
|
+
fill: vi.fn().mockResolvedValue(undefined),
|
|
87
|
+
};
|
|
88
|
+
locator.first.mockReturnValue(locator);
|
|
89
|
+
locator.nth.mockReturnValue(locator);
|
|
90
|
+
return locator;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface MockPage {
|
|
94
|
+
close: ReturnType<typeof vi.fn>;
|
|
95
|
+
goto: ReturnType<typeof vi.fn>;
|
|
96
|
+
click: ReturnType<typeof vi.fn>;
|
|
97
|
+
locator: ReturnType<typeof vi.fn>;
|
|
98
|
+
waitForSelector: ReturnType<typeof vi.fn>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function makeMockPage(): MockPage {
|
|
102
|
+
const defaultLocator = makeMockLocator(0);
|
|
103
|
+
return {
|
|
104
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
105
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
106
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
107
|
+
locator: vi.fn().mockReturnValue(defaultLocator),
|
|
108
|
+
waitForSelector: vi.fn().mockResolvedValue({ textContent: vi.fn().mockResolvedValue('') }),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let currentMockPage: MockPage;
|
|
113
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
114
|
+
|
|
115
|
+
vi.mock('playwright', async () => ({
|
|
116
|
+
chromium: {
|
|
117
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
118
|
+
},
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Dynamic import AFTER mocks are hoisted
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
const { CrudFlowTesterAgent } = await import('../25-crud-flow-tester');
|
|
126
|
+
|
|
127
|
+
function createAgent(
|
|
128
|
+
config: ValidatedConfig = makeConfig(),
|
|
129
|
+
phase: Phase = 'beta',
|
|
130
|
+
): InstanceType<typeof CrudFlowTesterAgent> {
|
|
131
|
+
return new CrudFlowTesterAgent(config, phase, '/tmp/test-run');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Tests
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
describe('CrudFlowTesterAgent', () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
141
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
142
|
+
|
|
143
|
+
currentMockPage = makeMockPage();
|
|
144
|
+
mockBrowser = {
|
|
145
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
146
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Identity
|
|
151
|
+
it('has agentId 25 and correct agentName', () => {
|
|
152
|
+
const agent = createAgent();
|
|
153
|
+
expect(agent.agentId).toBe(25);
|
|
154
|
+
expect(agent.agentName).toBe('CRUD Flow Tester');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// preFlight
|
|
158
|
+
it('throws when modules array is empty', async () => {
|
|
159
|
+
const config = makeConfig({ modules: [] });
|
|
160
|
+
const agent = createAgent(config);
|
|
161
|
+
const result = await agent.run();
|
|
162
|
+
|
|
163
|
+
expect(result.status).toBe('failed');
|
|
164
|
+
expect(result.findings[0].description).toContain('module');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('resolves environment in preFlight', async () => {
|
|
168
|
+
const agent = createAgent();
|
|
169
|
+
const result = await agent.run();
|
|
170
|
+
// Should not have a preFlight error
|
|
171
|
+
expect(result.findings.every(f => !f.description.includes('preFlight'))).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// No CRUD buttons found
|
|
175
|
+
it('produces low finding when no CRUD actions found for a module', async () => {
|
|
176
|
+
// All locator().count() returns 0 by default
|
|
177
|
+
const agent = createAgent();
|
|
178
|
+
const result = await agent.run();
|
|
179
|
+
|
|
180
|
+
const noCrudFindings = result.findings.filter(f => f.description.includes('No CRUD actions'));
|
|
181
|
+
expect(noCrudFindings.length).toBeGreaterThan(0);
|
|
182
|
+
expect(noCrudFindings[0].severity).toBe('low');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// CRUD buttons found — create flow
|
|
186
|
+
it('clicks create button when found and tests the flow', async () => {
|
|
187
|
+
const createLocator = makeMockLocator(1);
|
|
188
|
+
const formLocator = makeMockLocator(1);
|
|
189
|
+
const inputLocator = makeMockLocator(1);
|
|
190
|
+
const submitLocator = makeMockLocator(1);
|
|
191
|
+
const editLocator = makeMockLocator(0);
|
|
192
|
+
const deleteLocator = makeMockLocator(0);
|
|
193
|
+
|
|
194
|
+
inputLocator.fill = vi.fn().mockResolvedValue(undefined);
|
|
195
|
+
inputLocator.nth = vi.fn().mockReturnValue(inputLocator);
|
|
196
|
+
|
|
197
|
+
currentMockPage.locator.mockImplementation((sel: string) => {
|
|
198
|
+
if (sel.includes('Create') || sel.includes('Add') || sel.includes('New')) return createLocator;
|
|
199
|
+
if (sel.includes('form') || sel.includes('dialog') || sel.includes('modal')) return formLocator;
|
|
200
|
+
if (sel.includes('input[type="text"]')) return inputLocator;
|
|
201
|
+
if (sel.includes('submit') || sel.includes('Save')) return submitLocator;
|
|
202
|
+
if (sel.includes('Edit')) return editLocator;
|
|
203
|
+
if (sel.includes('Delete') || sel.includes('Remove')) return deleteLocator;
|
|
204
|
+
return makeMockLocator(0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
currentMockPage.waitForSelector.mockResolvedValue({});
|
|
208
|
+
|
|
209
|
+
const agent = createAgent();
|
|
210
|
+
const result = await agent.run();
|
|
211
|
+
|
|
212
|
+
expect(createLocator.first).toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Create fails
|
|
216
|
+
it('produces medium finding when create action fails', async () => {
|
|
217
|
+
const createLocator = makeMockLocator(1);
|
|
218
|
+
createLocator.first.mockReturnValue({
|
|
219
|
+
click: vi.fn().mockRejectedValue(new Error('Click failed')),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const editLocator = makeMockLocator(0);
|
|
223
|
+
const deleteLocator = makeMockLocator(0);
|
|
224
|
+
|
|
225
|
+
currentMockPage.locator.mockImplementation((sel: string) => {
|
|
226
|
+
if (sel.includes('Create') || sel.includes('Add') || sel.includes('New')) return createLocator;
|
|
227
|
+
if (sel.includes('Edit')) return editLocator;
|
|
228
|
+
if (sel.includes('Delete') || sel.includes('Remove')) return deleteLocator;
|
|
229
|
+
return makeMockLocator(0);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const agent = createAgent();
|
|
233
|
+
const result = await agent.run();
|
|
234
|
+
|
|
235
|
+
const createFailed = result.findings.filter(f => f.description.includes('Create action failed'));
|
|
236
|
+
expect(createFailed.length).toBeGreaterThan(0);
|
|
237
|
+
expect(createFailed[0].severity).toBe('medium');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Delete fails
|
|
241
|
+
it('produces medium finding when delete action fails', async () => {
|
|
242
|
+
const createLocator = makeMockLocator(0);
|
|
243
|
+
const editLocator = makeMockLocator(0);
|
|
244
|
+
const deleteLocator = makeMockLocator(1);
|
|
245
|
+
deleteLocator.first.mockReturnValue({
|
|
246
|
+
click: vi.fn().mockRejectedValue(new Error('Delete error')),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
currentMockPage.locator.mockImplementation((sel: string) => {
|
|
250
|
+
if (sel.includes('Create') || sel.includes('Add') || sel.includes('New')) return createLocator;
|
|
251
|
+
if (sel.includes('Edit')) return editLocator;
|
|
252
|
+
if (sel.includes('Delete') || sel.includes('Remove')) return deleteLocator;
|
|
253
|
+
return makeMockLocator(0);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const agent = createAgent();
|
|
257
|
+
const result = await agent.run();
|
|
258
|
+
|
|
259
|
+
const deleteFailed = result.findings.filter(f => f.description.includes('Delete action failed'));
|
|
260
|
+
expect(deleteFailed.length).toBeGreaterThan(0);
|
|
261
|
+
expect(deleteFailed[0].severity).toBe('medium');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Login failure — graceful degradation
|
|
265
|
+
it('produces medium finding and continues when login fails', async () => {
|
|
266
|
+
loginMock.mockRejectedValue(new Error('Invalid credentials'));
|
|
267
|
+
|
|
268
|
+
const agent = createAgent();
|
|
269
|
+
const result = await agent.run();
|
|
270
|
+
|
|
271
|
+
const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
|
|
272
|
+
expect(loginFinding).toBeDefined();
|
|
273
|
+
expect(loginFinding!.severity).toBe('medium');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// No credentials — graceful
|
|
277
|
+
it('produces medium finding when no credentials configured', async () => {
|
|
278
|
+
const config = makeConfig({
|
|
279
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
280
|
+
});
|
|
281
|
+
const agent = createAgent(config);
|
|
282
|
+
const result = await agent.run();
|
|
283
|
+
|
|
284
|
+
const noCredsFinding = result.findings.find(f => f.id.includes('no-credentials'));
|
|
285
|
+
expect(noCredsFinding).toBeDefined();
|
|
286
|
+
expect(noCredsFinding!.severity).toBe('medium');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Playwright crash — graceful
|
|
290
|
+
it('produces medium finding when Playwright crashes', async () => {
|
|
291
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Browser crashed'));
|
|
292
|
+
|
|
293
|
+
const agent = createAgent();
|
|
294
|
+
const result = await agent.run();
|
|
295
|
+
|
|
296
|
+
const pwFinding = result.findings.find(f => f.id.includes('playwright-failure'));
|
|
297
|
+
expect(pwFinding).toBeDefined();
|
|
298
|
+
expect(pwFinding!.severity).toBe('medium');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Navigation failure for a module
|
|
302
|
+
it('produces medium finding when module navigation fails', async () => {
|
|
303
|
+
currentMockPage.goto.mockRejectedValue(new Error('Navigation failed'));
|
|
304
|
+
|
|
305
|
+
const agent = createAgent();
|
|
306
|
+
const result = await agent.run();
|
|
307
|
+
|
|
308
|
+
const navFinding = result.findings.find(f => f.id.includes('nav-failed'));
|
|
309
|
+
expect(navFinding).toBeDefined();
|
|
310
|
+
expect(navFinding!.severity).toBe('medium');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Browser is closed in finally block
|
|
314
|
+
it('closes browser even when an error occurs', async () => {
|
|
315
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Launch error'));
|
|
316
|
+
|
|
317
|
+
const agent = createAgent();
|
|
318
|
+
await agent.run();
|
|
319
|
+
|
|
320
|
+
expect(mockBrowser.close).toHaveBeenCalled();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Phase } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
name: 'test',
|
|
13
|
+
auth: {
|
|
14
|
+
loginUrl: 'http://localhost/login',
|
|
15
|
+
credentials: {
|
|
16
|
+
admin: { email: 'admin@test.com', password: 'secret' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
modules: [
|
|
20
|
+
{ id: 'dashboard', route: '/dashboard', sidebarIcon: true },
|
|
21
|
+
],
|
|
22
|
+
environments: {
|
|
23
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
24
|
+
},
|
|
25
|
+
portals: [],
|
|
26
|
+
breakpoints: [],
|
|
27
|
+
integrations: [],
|
|
28
|
+
workers: [],
|
|
29
|
+
workflows: [],
|
|
30
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
31
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
32
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
33
|
+
agentClassification: { blocking: [], advisory: [26] },
|
|
34
|
+
thresholds: {
|
|
35
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
36
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
37
|
+
apiResponseTime: 2000,
|
|
38
|
+
bundleSizeLimit: 500000,
|
|
39
|
+
maxConsoleErrors: 0,
|
|
40
|
+
},
|
|
41
|
+
disabledAgents: [],
|
|
42
|
+
disabledAgentSet: new Set<number>(),
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Mock fetch (preFlight URL reachability)
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
const fetchMock = vi.fn();
|
|
52
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Mock helpers
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
59
|
+
|
|
60
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
61
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
62
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
63
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
64
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Mock Playwright
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
interface MockLocator {
|
|
72
|
+
count: ReturnType<typeof vi.fn>;
|
|
73
|
+
first: ReturnType<typeof vi.fn>;
|
|
74
|
+
nth: ReturnType<typeof vi.fn>;
|
|
75
|
+
click: ReturnType<typeof vi.fn>;
|
|
76
|
+
fill: ReturnType<typeof vi.fn>;
|
|
77
|
+
check: ReturnType<typeof vi.fn>;
|
|
78
|
+
selectOption: ReturnType<typeof vi.fn>;
|
|
79
|
+
locator: ReturnType<typeof vi.fn>;
|
|
80
|
+
evaluate: ReturnType<typeof vi.fn>;
|
|
81
|
+
getAttribute: ReturnType<typeof vi.fn>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function makeMockLocator(countVal = 0): MockLocator {
|
|
85
|
+
const locator: MockLocator = {
|
|
86
|
+
count: vi.fn().mockResolvedValue(countVal),
|
|
87
|
+
first: vi.fn(),
|
|
88
|
+
nth: vi.fn(),
|
|
89
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
90
|
+
fill: vi.fn().mockResolvedValue(undefined),
|
|
91
|
+
check: vi.fn().mockResolvedValue(undefined),
|
|
92
|
+
selectOption: vi.fn().mockResolvedValue(undefined),
|
|
93
|
+
locator: vi.fn().mockReturnValue({
|
|
94
|
+
count: vi.fn().mockResolvedValue(0),
|
|
95
|
+
nth: vi.fn().mockReturnValue({
|
|
96
|
+
getAttribute: vi.fn().mockResolvedValue(null),
|
|
97
|
+
}),
|
|
98
|
+
}),
|
|
99
|
+
evaluate: vi.fn().mockResolvedValue('input'),
|
|
100
|
+
getAttribute: vi.fn().mockResolvedValue('text'),
|
|
101
|
+
};
|
|
102
|
+
locator.first.mockReturnValue(locator);
|
|
103
|
+
locator.nth.mockReturnValue(locator);
|
|
104
|
+
return locator;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface MockPage {
|
|
108
|
+
close: ReturnType<typeof vi.fn>;
|
|
109
|
+
goto: ReturnType<typeof vi.fn>;
|
|
110
|
+
locator: ReturnType<typeof vi.fn>;
|
|
111
|
+
waitForSelector: ReturnType<typeof vi.fn>;
|
|
112
|
+
waitForTimeout: ReturnType<typeof vi.fn>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function makeMockPage(): MockPage {
|
|
116
|
+
const defaultLocator = makeMockLocator(0);
|
|
117
|
+
return {
|
|
118
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
119
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
120
|
+
locator: vi.fn().mockReturnValue(defaultLocator),
|
|
121
|
+
waitForSelector: vi.fn().mockResolvedValue({ textContent: vi.fn().mockResolvedValue('') }),
|
|
122
|
+
waitForTimeout: vi.fn().mockResolvedValue(undefined),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let currentMockPage: MockPage;
|
|
127
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
128
|
+
|
|
129
|
+
vi.mock('playwright', async () => ({
|
|
130
|
+
chromium: {
|
|
131
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
132
|
+
},
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Dynamic import AFTER mocks are hoisted
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
const { FormValidatorAgent } = await import('../26-form-validator');
|
|
140
|
+
|
|
141
|
+
function createAgent(
|
|
142
|
+
config: ValidatedConfig = makeConfig(),
|
|
143
|
+
phase: Phase = 'beta',
|
|
144
|
+
): InstanceType<typeof FormValidatorAgent> {
|
|
145
|
+
return new FormValidatorAgent(config, phase, '/tmp/test-run');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Tests
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
describe('FormValidatorAgent', () => {
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
155
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
156
|
+
|
|
157
|
+
currentMockPage = makeMockPage();
|
|
158
|
+
mockBrowser = {
|
|
159
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
160
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Identity
|
|
165
|
+
it('has agentId 26 and correct agentName', () => {
|
|
166
|
+
const agent = createAgent();
|
|
167
|
+
expect(agent.agentId).toBe(26);
|
|
168
|
+
expect(agent.agentName).toBe('Form Validator');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// preFlight
|
|
172
|
+
it('throws when modules array is empty', async () => {
|
|
173
|
+
const config = makeConfig({ modules: [] });
|
|
174
|
+
const agent = createAgent(config);
|
|
175
|
+
const result = await agent.run();
|
|
176
|
+
|
|
177
|
+
expect(result.status).toBe('failed');
|
|
178
|
+
expect(result.findings[0].description).toContain('module');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// No forms found
|
|
182
|
+
it('produces low finding when no forms found in a module', async () => {
|
|
183
|
+
// form locator returns count 0
|
|
184
|
+
const formLocator = makeMockLocator(0);
|
|
185
|
+
currentMockPage.locator.mockImplementation((sel: string) => {
|
|
186
|
+
if (sel === 'form') return formLocator;
|
|
187
|
+
return makeMockLocator(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const agent = createAgent();
|
|
191
|
+
const result = await agent.run();
|
|
192
|
+
|
|
193
|
+
const noFormFindings = result.findings.filter(f => f.description.includes('No forms found'));
|
|
194
|
+
expect(noFormFindings.length).toBeGreaterThan(0);
|
|
195
|
+
expect(noFormFindings[0].severity).toBe('low');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Form accepts empty submission (high finding)
|
|
199
|
+
it('produces high finding when form accepts empty submission without validation', async () => {
|
|
200
|
+
const submitLocator = makeMockLocator(1);
|
|
201
|
+
const inputLocator = makeMockLocator(2);
|
|
202
|
+
const errorLocator = makeMockLocator(0); // No validation errors
|
|
203
|
+
|
|
204
|
+
const formLocator = makeMockLocator(1);
|
|
205
|
+
formLocator.nth = vi.fn().mockReturnValue(formLocator);
|
|
206
|
+
formLocator.locator.mockImplementation((sel: string) => {
|
|
207
|
+
if (sel.includes('submit') || sel.includes('Submit') || sel.includes('Save')) return submitLocator;
|
|
208
|
+
return inputLocator;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
currentMockPage.locator.mockImplementation((sel: string) => {
|
|
212
|
+
if (sel === 'form') return formLocator;
|
|
213
|
+
// error selectors checked via page.locator(errorSelector)
|
|
214
|
+
if (sel.includes('error') || sel.includes('invalid') || sel.includes('red') || sel.includes('alert')) return errorLocator;
|
|
215
|
+
return makeMockLocator(0);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const agent = createAgent();
|
|
219
|
+
const result = await agent.run();
|
|
220
|
+
|
|
221
|
+
const emptySubmitFinding = result.findings.filter(f =>
|
|
222
|
+
f.description.includes('accepts empty submission'),
|
|
223
|
+
);
|
|
224
|
+
expect(emptySubmitFinding.length).toBeGreaterThan(0);
|
|
225
|
+
expect(emptySubmitFinding[0].severity).toBe('high');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Form shows validation errors on empty submit
|
|
229
|
+
it('produces no finding when form shows validation errors on empty submit', async () => {
|
|
230
|
+
const submitLocator = makeMockLocator(1);
|
|
231
|
+
const inputLocator = makeMockLocator(2);
|
|
232
|
+
const errorLocator = makeMockLocator(3); // Validation errors present
|
|
233
|
+
|
|
234
|
+
const formLocator = makeMockLocator(1);
|
|
235
|
+
formLocator.nth = vi.fn().mockReturnValue(formLocator);
|
|
236
|
+
formLocator.locator.mockImplementation((sel: string) => {
|
|
237
|
+
if (sel.includes('submit') || sel.includes('Submit') || sel.includes('Save')) return submitLocator;
|
|
238
|
+
return inputLocator;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
currentMockPage.locator.mockImplementation((sel: string) => {
|
|
242
|
+
if (sel === 'form') return formLocator;
|
|
243
|
+
// Error selectors checked via page.locator
|
|
244
|
+
if (sel.includes('error') || sel.includes('invalid') || sel.includes('red') || sel.includes('alert') || sel.includes('field-error') || sel.includes('validation')) return errorLocator;
|
|
245
|
+
return makeMockLocator(0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const agent = createAgent();
|
|
249
|
+
const result = await agent.run();
|
|
250
|
+
|
|
251
|
+
const emptySubmitFinding = result.findings.filter(f =>
|
|
252
|
+
f.description.includes('accepts empty submission'),
|
|
253
|
+
);
|
|
254
|
+
expect(emptySubmitFinding.length).toBe(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Login failure — graceful degradation
|
|
258
|
+
it('produces medium finding and continues when login fails', async () => {
|
|
259
|
+
loginMock.mockRejectedValue(new Error('Invalid credentials'));
|
|
260
|
+
|
|
261
|
+
const agent = createAgent();
|
|
262
|
+
const result = await agent.run();
|
|
263
|
+
|
|
264
|
+
const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
|
|
265
|
+
expect(loginFinding).toBeDefined();
|
|
266
|
+
expect(loginFinding!.severity).toBe('medium');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// No credentials
|
|
270
|
+
it('produces medium finding when no credentials configured', async () => {
|
|
271
|
+
const config = makeConfig({
|
|
272
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
273
|
+
});
|
|
274
|
+
const agent = createAgent(config);
|
|
275
|
+
const result = await agent.run();
|
|
276
|
+
|
|
277
|
+
const noCredsFinding = result.findings.find(f => f.id.includes('no-credentials'));
|
|
278
|
+
expect(noCredsFinding).toBeDefined();
|
|
279
|
+
expect(noCredsFinding!.severity).toBe('medium');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Playwright crash
|
|
283
|
+
it('produces medium finding when Playwright crashes', async () => {
|
|
284
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Browser crashed'));
|
|
285
|
+
|
|
286
|
+
const agent = createAgent();
|
|
287
|
+
const result = await agent.run();
|
|
288
|
+
|
|
289
|
+
const pwFinding = result.findings.find(f => f.id.includes('playwright-failure'));
|
|
290
|
+
expect(pwFinding).toBeDefined();
|
|
291
|
+
expect(pwFinding!.severity).toBe('medium');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Module navigation failure
|
|
295
|
+
it('produces medium finding when module navigation fails', async () => {
|
|
296
|
+
currentMockPage.goto.mockRejectedValue(new Error('Navigation failed'));
|
|
297
|
+
|
|
298
|
+
const agent = createAgent();
|
|
299
|
+
const result = await agent.run();
|
|
300
|
+
|
|
301
|
+
const navFinding = result.findings.find(f => f.id.includes('nav-failed'));
|
|
302
|
+
expect(navFinding).toBeDefined();
|
|
303
|
+
expect(navFinding!.severity).toBe('medium');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Browser closed in finally
|
|
307
|
+
it('closes browser even when an error occurs', async () => {
|
|
308
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Launch error'));
|
|
309
|
+
|
|
310
|
+
const agent = createAgent();
|
|
311
|
+
await agent.run();
|
|
312
|
+
|
|
313
|
+
expect(mockBrowser.close).toHaveBeenCalled();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Submit button click failure
|
|
317
|
+
it('produces medium finding when submit button click throws', async () => {
|
|
318
|
+
const submitLocator = makeMockLocator(1);
|
|
319
|
+
submitLocator.first.mockReturnValue({
|
|
320
|
+
click: vi.fn().mockRejectedValue(new Error('Submit click failed')),
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const inputLocator = makeMockLocator(2);
|
|
324
|
+
|
|
325
|
+
const formLocator = makeMockLocator(1);
|
|
326
|
+
formLocator.nth = vi.fn().mockReturnValue(formLocator);
|
|
327
|
+
// form.locator() returns inputs or submit button depending on selector
|
|
328
|
+
formLocator.locator.mockImplementation((sel: string) => {
|
|
329
|
+
if (sel.includes('submit') || sel.includes('Submit') || sel.includes('Save')) return submitLocator;
|
|
330
|
+
return inputLocator;
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
currentMockPage.locator.mockImplementation((sel: string) => {
|
|
334
|
+
if (sel === 'form') return formLocator;
|
|
335
|
+
return makeMockLocator(0);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const agent = createAgent();
|
|
339
|
+
const result = await agent.run();
|
|
340
|
+
|
|
341
|
+
const submitError = result.findings.filter(f => f.description.includes('submit test failed'));
|
|
342
|
+
expect(submitError.length).toBeGreaterThan(0);
|
|
343
|
+
expect(submitError[0].severity).toBe('medium');
|
|
344
|
+
});
|
|
345
|
+
});
|