@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,302 @@
|
|
|
1
|
+
import type { Browser, Page } from 'playwright';
|
|
2
|
+
import type { Finding } from '../core/types';
|
|
3
|
+
import { BaseAgent } from './base-agent';
|
|
4
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
5
|
+
import { login } from '../helpers/navigation';
|
|
6
|
+
|
|
7
|
+
/** Timeout (ms) for individual module CRUD operations. */
|
|
8
|
+
const OP_TIMEOUT_MS = 5_000;
|
|
9
|
+
|
|
10
|
+
/** Selectors that indicate common CRUD action buttons. */
|
|
11
|
+
const CREATE_SELECTORS = [
|
|
12
|
+
'button:has-text("Create")',
|
|
13
|
+
'button:has-text("Add")',
|
|
14
|
+
'button:has-text("New")',
|
|
15
|
+
'[data-testid*="create"]',
|
|
16
|
+
'[data-testid*="add"]',
|
|
17
|
+
'[data-testid*="new"]',
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
const EDIT_SELECTORS = [
|
|
21
|
+
'button:has-text("Edit")',
|
|
22
|
+
'[data-testid*="edit"]',
|
|
23
|
+
'a:has-text("Edit")',
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
const DELETE_SELECTORS = [
|
|
27
|
+
'button:has-text("Delete")',
|
|
28
|
+
'[data-testid*="delete"]',
|
|
29
|
+
'button:has-text("Remove")',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
const SUCCESS_SELECTORS = [
|
|
33
|
+
'[class*="toast"]',
|
|
34
|
+
'[role="alert"]',
|
|
35
|
+
'[data-testid*="success"]',
|
|
36
|
+
'.success',
|
|
37
|
+
'.notification',
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
export class CrudFlowTesterAgent extends BaseAgent {
|
|
41
|
+
readonly agentId = 25;
|
|
42
|
+
readonly agentName = 'CRUD Flow Tester';
|
|
43
|
+
private baseUrl = '';
|
|
44
|
+
|
|
45
|
+
protected async preFlight(): Promise<void> {
|
|
46
|
+
if (!this.config.modules || this.config.modules.length === 0) {
|
|
47
|
+
throw new Error('CrudFlowTesterAgent requires at least one module in config.modules');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
51
|
+
this.baseUrl = env.baseUrl;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async execute(): Promise<Finding[]> {
|
|
55
|
+
const findings: Finding[] = [];
|
|
56
|
+
|
|
57
|
+
const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
|
|
58
|
+
const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
|
|
59
|
+
const credentials =
|
|
60
|
+
this.config.auth.credentials?.['admin'] ??
|
|
61
|
+
Object.values(this.config.auth.credentials ?? {})[0];
|
|
62
|
+
|
|
63
|
+
if (!credentials) {
|
|
64
|
+
findings.push({
|
|
65
|
+
id: `${this.agentId}-no-credentials`,
|
|
66
|
+
type: 'infra-issue',
|
|
67
|
+
severity: 'medium',
|
|
68
|
+
agentId: this.agentId,
|
|
69
|
+
module: 'crud-flow-tester',
|
|
70
|
+
description: 'No credentials configured — continuing without authentication',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { chromium } = await import('playwright');
|
|
75
|
+
|
|
76
|
+
let browser: Browser | null = null;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
browser = await chromium.launch({ headless: true });
|
|
80
|
+
const page = await browser.newPage();
|
|
81
|
+
|
|
82
|
+
if (credentials) {
|
|
83
|
+
try {
|
|
84
|
+
await login(page, credentials, loginUrl);
|
|
85
|
+
} catch (loginError) {
|
|
86
|
+
findings.push({
|
|
87
|
+
id: `${this.agentId}-login-failed`,
|
|
88
|
+
type: 'infra-issue',
|
|
89
|
+
severity: 'medium',
|
|
90
|
+
agentId: this.agentId,
|
|
91
|
+
module: 'crud-flow-tester',
|
|
92
|
+
description: `Login failed — continuing anonymously: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const mod of this.config.modules) {
|
|
98
|
+
const moduleFindings = await this.testModuleCrud(page, mod.id, mod.route);
|
|
99
|
+
findings.push(...moduleFindings);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await page.close().catch(() => undefined);
|
|
103
|
+
} catch (playwrightError) {
|
|
104
|
+
findings.push({
|
|
105
|
+
id: `${this.agentId}-playwright-failure`,
|
|
106
|
+
type: 'infra-issue',
|
|
107
|
+
severity: 'medium',
|
|
108
|
+
agentId: this.agentId,
|
|
109
|
+
module: 'crud-flow-tester',
|
|
110
|
+
description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
|
|
111
|
+
});
|
|
112
|
+
} finally {
|
|
113
|
+
if (browser) await browser.close().catch(() => undefined);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return findings;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private async testModuleCrud(
|
|
120
|
+
page: Page,
|
|
121
|
+
moduleId: string,
|
|
122
|
+
moduleRoute: string,
|
|
123
|
+
): Promise<Finding[]> {
|
|
124
|
+
const findings: Finding[] = [];
|
|
125
|
+
const fullUrl = moduleRoute.startsWith('http')
|
|
126
|
+
? moduleRoute
|
|
127
|
+
: `${this.baseUrl}${moduleRoute}`;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await Promise.race([
|
|
131
|
+
page.goto(fullUrl, { waitUntil: 'domcontentloaded' }),
|
|
132
|
+
new Promise((_, reject) =>
|
|
133
|
+
setTimeout(() => reject(new Error('Navigation timeout')), OP_TIMEOUT_MS),
|
|
134
|
+
),
|
|
135
|
+
]);
|
|
136
|
+
} catch {
|
|
137
|
+
findings.push({
|
|
138
|
+
id: `${this.agentId}-nav-failed-${moduleId}`,
|
|
139
|
+
type: 'test-bug',
|
|
140
|
+
severity: 'medium',
|
|
141
|
+
agentId: this.agentId,
|
|
142
|
+
module: moduleId,
|
|
143
|
+
description: `Failed to navigate to module ${moduleId} at ${moduleRoute}`,
|
|
144
|
+
});
|
|
145
|
+
return findings;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Discover create buttons
|
|
149
|
+
const createSelector = CREATE_SELECTORS.join(', ');
|
|
150
|
+
let hasCreateButton = false;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const createCount = await Promise.race([
|
|
154
|
+
page.locator(createSelector).count(),
|
|
155
|
+
new Promise<number>((_, reject) =>
|
|
156
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
157
|
+
),
|
|
158
|
+
]) as number;
|
|
159
|
+
|
|
160
|
+
hasCreateButton = createCount > 0;
|
|
161
|
+
} catch {
|
|
162
|
+
// Timeout or error discovering create buttons
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!hasCreateButton) {
|
|
166
|
+
// Check for edit/delete as well
|
|
167
|
+
const editSelector = EDIT_SELECTORS.join(', ');
|
|
168
|
+
const deleteSelector = DELETE_SELECTORS.join(', ');
|
|
169
|
+
|
|
170
|
+
let hasEditOrDelete = false;
|
|
171
|
+
try {
|
|
172
|
+
const editCount = await Promise.race([
|
|
173
|
+
page.locator(editSelector).count(),
|
|
174
|
+
new Promise<number>((_, reject) =>
|
|
175
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
176
|
+
),
|
|
177
|
+
]) as number;
|
|
178
|
+
|
|
179
|
+
const deleteCount = await Promise.race([
|
|
180
|
+
page.locator(deleteSelector).count(),
|
|
181
|
+
new Promise<number>((_, reject) =>
|
|
182
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
183
|
+
),
|
|
184
|
+
]) as number;
|
|
185
|
+
|
|
186
|
+
hasEditOrDelete = editCount > 0 || deleteCount > 0;
|
|
187
|
+
} catch {
|
|
188
|
+
// Timeout or error
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!hasEditOrDelete) {
|
|
192
|
+
findings.push({
|
|
193
|
+
id: `${this.agentId}-no-crud-${moduleId}`,
|
|
194
|
+
type: 'test-bug',
|
|
195
|
+
severity: 'low',
|
|
196
|
+
agentId: this.agentId,
|
|
197
|
+
module: moduleId,
|
|
198
|
+
description: `No CRUD actions found for module ${moduleId}`,
|
|
199
|
+
});
|
|
200
|
+
return findings;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Attempt create flow
|
|
205
|
+
if (hasCreateButton) {
|
|
206
|
+
try {
|
|
207
|
+
await Promise.race([
|
|
208
|
+
page.locator(createSelector).first().click(),
|
|
209
|
+
new Promise((_, reject) =>
|
|
210
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
211
|
+
),
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
// Look for form or modal
|
|
215
|
+
const formCount = await page.locator('form, [role="dialog"], .modal').count();
|
|
216
|
+
if (formCount > 0) {
|
|
217
|
+
// Fill text inputs with test data
|
|
218
|
+
const inputs = page.locator('form input[type="text"], [role="dialog"] input[type="text"]');
|
|
219
|
+
const inputCount = await inputs.count();
|
|
220
|
+
for (let i = 0; i < inputCount; i++) {
|
|
221
|
+
await inputs.nth(i).fill(`Test Data ${i + 1}`).catch(() => undefined);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Try submit
|
|
225
|
+
const submitBtn = page.locator(
|
|
226
|
+
'button[type="submit"], button:has-text("Save"), button:has-text("Submit"), button:has-text("Create")',
|
|
227
|
+
);
|
|
228
|
+
if (await submitBtn.count() > 0) {
|
|
229
|
+
await submitBtn.first().click().catch(() => undefined);
|
|
230
|
+
|
|
231
|
+
// Check for success indicator
|
|
232
|
+
const successSelector = SUCCESS_SELECTORS.join(', ');
|
|
233
|
+
try {
|
|
234
|
+
await page.waitForSelector(successSelector, { timeout: OP_TIMEOUT_MS });
|
|
235
|
+
} catch {
|
|
236
|
+
findings.push({
|
|
237
|
+
id: `${this.agentId}-create-no-feedback-${moduleId}`,
|
|
238
|
+
type: 'test-bug',
|
|
239
|
+
severity: 'medium',
|
|
240
|
+
agentId: this.agentId,
|
|
241
|
+
module: moduleId,
|
|
242
|
+
description: `Create action in module ${moduleId} did not produce a visible success/error response`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (createError) {
|
|
248
|
+
findings.push({
|
|
249
|
+
id: `${this.agentId}-create-failed-${moduleId}`,
|
|
250
|
+
type: 'test-bug',
|
|
251
|
+
severity: 'medium',
|
|
252
|
+
agentId: this.agentId,
|
|
253
|
+
module: moduleId,
|
|
254
|
+
description: `Create action failed for module ${moduleId}: ${createError instanceof Error ? createError.message.split('\n')[0] : String(createError)}`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Check for delete buttons on the page
|
|
260
|
+
const deleteSelector = DELETE_SELECTORS.join(', ');
|
|
261
|
+
try {
|
|
262
|
+
const deleteCount = await Promise.race([
|
|
263
|
+
page.locator(deleteSelector).count(),
|
|
264
|
+
new Promise<number>((_, reject) =>
|
|
265
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
266
|
+
),
|
|
267
|
+
]) as number;
|
|
268
|
+
|
|
269
|
+
if (deleteCount > 0) {
|
|
270
|
+
try {
|
|
271
|
+
await Promise.race([
|
|
272
|
+
page.locator(deleteSelector).first().click(),
|
|
273
|
+
new Promise((_, reject) =>
|
|
274
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
275
|
+
),
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
// Look for confirm dialog
|
|
279
|
+
const confirmBtn = page.locator(
|
|
280
|
+
'button:has-text("Confirm"), button:has-text("Yes"), button:has-text("OK")',
|
|
281
|
+
);
|
|
282
|
+
if (await confirmBtn.count() > 0) {
|
|
283
|
+
await confirmBtn.first().click().catch(() => undefined);
|
|
284
|
+
}
|
|
285
|
+
} catch (deleteError) {
|
|
286
|
+
findings.push({
|
|
287
|
+
id: `${this.agentId}-delete-failed-${moduleId}`,
|
|
288
|
+
type: 'test-bug',
|
|
289
|
+
severity: 'medium',
|
|
290
|
+
agentId: this.agentId,
|
|
291
|
+
module: moduleId,
|
|
292
|
+
description: `Delete action failed for module ${moduleId}: ${deleteError instanceof Error ? deleteError.message.split('\n')[0] : String(deleteError)}`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
// Timeout discovering delete buttons — non-critical
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return findings;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { Browser, Page } from 'playwright';
|
|
2
|
+
import type { Finding } from '../core/types';
|
|
3
|
+
import { BaseAgent } from './base-agent';
|
|
4
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
5
|
+
import { login } from '../helpers/navigation';
|
|
6
|
+
|
|
7
|
+
/** Timeout (ms) for individual form test operations. */
|
|
8
|
+
const OP_TIMEOUT_MS = 5_000;
|
|
9
|
+
|
|
10
|
+
/** Selectors that indicate validation error messages. */
|
|
11
|
+
const VALIDATION_ERROR_SELECTORS = [
|
|
12
|
+
'.error',
|
|
13
|
+
'[aria-invalid="true"]',
|
|
14
|
+
'.text-red',
|
|
15
|
+
'[class*="error"]',
|
|
16
|
+
'[role="alert"]',
|
|
17
|
+
'.field-error',
|
|
18
|
+
'.validation-error',
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export class FormValidatorAgent extends BaseAgent {
|
|
22
|
+
readonly agentId = 26;
|
|
23
|
+
readonly agentName = 'Form Validator';
|
|
24
|
+
private baseUrl = '';
|
|
25
|
+
|
|
26
|
+
protected async preFlight(): Promise<void> {
|
|
27
|
+
if (!this.config.modules || this.config.modules.length === 0) {
|
|
28
|
+
throw new Error('FormValidatorAgent requires at least one module in config.modules');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
32
|
+
this.baseUrl = env.baseUrl;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected async execute(): Promise<Finding[]> {
|
|
36
|
+
const findings: Finding[] = [];
|
|
37
|
+
|
|
38
|
+
const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
|
|
39
|
+
const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
|
|
40
|
+
const credentials =
|
|
41
|
+
this.config.auth.credentials?.['admin'] ??
|
|
42
|
+
Object.values(this.config.auth.credentials ?? {})[0];
|
|
43
|
+
|
|
44
|
+
if (!credentials) {
|
|
45
|
+
findings.push({
|
|
46
|
+
id: `${this.agentId}-no-credentials`,
|
|
47
|
+
type: 'infra-issue',
|
|
48
|
+
severity: 'medium',
|
|
49
|
+
agentId: this.agentId,
|
|
50
|
+
module: 'form-validator',
|
|
51
|
+
description: 'No credentials configured — continuing without authentication',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { chromium } = await import('playwright');
|
|
56
|
+
|
|
57
|
+
let browser: Browser | null = null;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
browser = await chromium.launch({ headless: true });
|
|
61
|
+
const page = await browser.newPage();
|
|
62
|
+
|
|
63
|
+
if (credentials) {
|
|
64
|
+
try {
|
|
65
|
+
await login(page, credentials, loginUrl);
|
|
66
|
+
} catch (loginError) {
|
|
67
|
+
findings.push({
|
|
68
|
+
id: `${this.agentId}-login-failed`,
|
|
69
|
+
type: 'infra-issue',
|
|
70
|
+
severity: 'medium',
|
|
71
|
+
agentId: this.agentId,
|
|
72
|
+
module: 'form-validator',
|
|
73
|
+
description: `Login failed — continuing anonymously: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const mod of this.config.modules) {
|
|
79
|
+
const moduleFindings = await this.testModuleForms(page, mod.id, mod.route);
|
|
80
|
+
findings.push(...moduleFindings);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await page.close().catch(() => undefined);
|
|
84
|
+
} catch (playwrightError) {
|
|
85
|
+
findings.push({
|
|
86
|
+
id: `${this.agentId}-playwright-failure`,
|
|
87
|
+
type: 'infra-issue',
|
|
88
|
+
severity: 'medium',
|
|
89
|
+
agentId: this.agentId,
|
|
90
|
+
module: 'form-validator',
|
|
91
|
+
description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
|
|
92
|
+
});
|
|
93
|
+
} finally {
|
|
94
|
+
if (browser) await browser.close().catch(() => undefined);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return findings;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async testModuleForms(
|
|
101
|
+
page: Page,
|
|
102
|
+
moduleId: string,
|
|
103
|
+
moduleRoute: string,
|
|
104
|
+
): Promise<Finding[]> {
|
|
105
|
+
const findings: Finding[] = [];
|
|
106
|
+
const fullUrl = moduleRoute.startsWith('http')
|
|
107
|
+
? moduleRoute
|
|
108
|
+
: `${this.baseUrl}${moduleRoute}`;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await Promise.race([
|
|
112
|
+
page.goto(fullUrl, { waitUntil: 'domcontentloaded' }),
|
|
113
|
+
new Promise((_, reject) =>
|
|
114
|
+
setTimeout(() => reject(new Error('Navigation timeout')), OP_TIMEOUT_MS),
|
|
115
|
+
),
|
|
116
|
+
]);
|
|
117
|
+
} catch {
|
|
118
|
+
findings.push({
|
|
119
|
+
id: `${this.agentId}-nav-failed-${moduleId}`,
|
|
120
|
+
type: 'test-bug',
|
|
121
|
+
severity: 'medium',
|
|
122
|
+
agentId: this.agentId,
|
|
123
|
+
module: moduleId,
|
|
124
|
+
description: `Failed to navigate to module ${moduleId} at ${moduleRoute}`,
|
|
125
|
+
});
|
|
126
|
+
return findings;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Find all forms
|
|
130
|
+
let formCount = 0;
|
|
131
|
+
try {
|
|
132
|
+
formCount = await Promise.race([
|
|
133
|
+
page.locator('form').count(),
|
|
134
|
+
new Promise<number>((_, reject) =>
|
|
135
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
136
|
+
),
|
|
137
|
+
]) as number;
|
|
138
|
+
} catch {
|
|
139
|
+
// Timeout discovering forms
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (formCount === 0) {
|
|
143
|
+
findings.push({
|
|
144
|
+
id: `${this.agentId}-no-forms-${moduleId}`,
|
|
145
|
+
type: 'test-bug',
|
|
146
|
+
severity: 'low',
|
|
147
|
+
agentId: this.agentId,
|
|
148
|
+
module: moduleId,
|
|
149
|
+
description: `No forms found in module ${moduleId}`,
|
|
150
|
+
});
|
|
151
|
+
return findings;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (let formIdx = 0; formIdx < formCount; formIdx++) {
|
|
155
|
+
const formFindings = await this.testSingleForm(page, moduleId, formIdx);
|
|
156
|
+
findings.push(...formFindings);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return findings;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async testSingleForm(
|
|
163
|
+
page: Page,
|
|
164
|
+
moduleId: string,
|
|
165
|
+
formIndex: number,
|
|
166
|
+
): Promise<Finding[]> {
|
|
167
|
+
const findings: Finding[] = [];
|
|
168
|
+
const form = page.locator('form').nth(formIndex);
|
|
169
|
+
|
|
170
|
+
// Count inputs
|
|
171
|
+
let inputCount = 0;
|
|
172
|
+
try {
|
|
173
|
+
inputCount = await Promise.race([
|
|
174
|
+
form.locator('input, textarea, select').count(),
|
|
175
|
+
new Promise<number>((_, reject) =>
|
|
176
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
177
|
+
),
|
|
178
|
+
]) as number;
|
|
179
|
+
} catch {
|
|
180
|
+
return findings;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (inputCount === 0) {
|
|
184
|
+
return findings;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Empty submit test: click submit without filling anything
|
|
188
|
+
const submitBtn = form.locator(
|
|
189
|
+
'button[type="submit"], input[type="submit"], button:has-text("Submit"), button:has-text("Save")',
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
let hasSubmitButton = false;
|
|
193
|
+
try {
|
|
194
|
+
hasSubmitButton = (await submitBtn.count()) > 0;
|
|
195
|
+
} catch {
|
|
196
|
+
return findings;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!hasSubmitButton) {
|
|
200
|
+
return findings;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await Promise.race([
|
|
205
|
+
submitBtn.first().click(),
|
|
206
|
+
new Promise((_, reject) =>
|
|
207
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
208
|
+
),
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
// Wait briefly for validation errors to appear
|
|
212
|
+
await page.waitForTimeout(500);
|
|
213
|
+
|
|
214
|
+
// Check for validation errors
|
|
215
|
+
const errorSelector = VALIDATION_ERROR_SELECTORS.join(', ');
|
|
216
|
+
let errorCount = 0;
|
|
217
|
+
try {
|
|
218
|
+
errorCount = await page.locator(errorSelector).count();
|
|
219
|
+
} catch {
|
|
220
|
+
// Error counting validation indicators
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (errorCount === 0) {
|
|
224
|
+
findings.push({
|
|
225
|
+
id: `${this.agentId}-empty-submit-${moduleId}-form${formIndex}`,
|
|
226
|
+
type: 'test-bug',
|
|
227
|
+
severity: 'high',
|
|
228
|
+
agentId: this.agentId,
|
|
229
|
+
module: moduleId,
|
|
230
|
+
description: `Form ${formIndex} in module ${moduleId} accepts empty submission without validation`,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
} catch (submitError) {
|
|
234
|
+
findings.push({
|
|
235
|
+
id: `${this.agentId}-submit-error-${moduleId}-form${formIndex}`,
|
|
236
|
+
type: 'test-bug',
|
|
237
|
+
severity: 'medium',
|
|
238
|
+
agentId: this.agentId,
|
|
239
|
+
module: moduleId,
|
|
240
|
+
description: `Form ${formIndex} submit test failed in module ${moduleId}: ${submitError instanceof Error ? submitError.message.split('\n')[0] : String(submitError)}`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Valid submit test: fill required inputs with test data
|
|
245
|
+
try {
|
|
246
|
+
const inputs = form.locator('input, textarea, select');
|
|
247
|
+
const count = await inputs.count();
|
|
248
|
+
for (let i = 0; i < count; i++) {
|
|
249
|
+
const input = inputs.nth(i);
|
|
250
|
+
const tagName = await input.evaluate((el) => el.tagName.toLowerCase());
|
|
251
|
+
const inputType = await input.getAttribute('type') ?? 'text';
|
|
252
|
+
|
|
253
|
+
if (tagName === 'select') {
|
|
254
|
+
// Select second option if available
|
|
255
|
+
const optionCount = await input.locator('option').count();
|
|
256
|
+
if (optionCount > 1) {
|
|
257
|
+
const value = await input.locator('option').nth(1).getAttribute('value');
|
|
258
|
+
if (value) {
|
|
259
|
+
await input.selectOption(value).catch(() => undefined);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else if (inputType === 'email') {
|
|
263
|
+
await input.fill('test@example.com').catch(() => undefined);
|
|
264
|
+
} else if (inputType === 'number') {
|
|
265
|
+
await input.fill('42').catch(() => undefined);
|
|
266
|
+
} else if (inputType === 'tel') {
|
|
267
|
+
await input.fill('+1234567890').catch(() => undefined);
|
|
268
|
+
} else if (inputType === 'checkbox' || inputType === 'radio') {
|
|
269
|
+
await input.check().catch(() => undefined);
|
|
270
|
+
} else if (tagName === 'textarea' || inputType === 'text' || inputType === 'search') {
|
|
271
|
+
await input.fill('Test input data').catch(() => undefined);
|
|
272
|
+
} else if (inputType === 'password') {
|
|
273
|
+
await input.fill('TestPassword123!').catch(() => undefined);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Submit with filled data
|
|
278
|
+
await Promise.race([
|
|
279
|
+
submitBtn.first().click(),
|
|
280
|
+
new Promise((_, reject) =>
|
|
281
|
+
setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
|
|
282
|
+
),
|
|
283
|
+
]);
|
|
284
|
+
} catch (validSubmitError) {
|
|
285
|
+
findings.push({
|
|
286
|
+
id: `${this.agentId}-valid-submit-error-${moduleId}-form${formIndex}`,
|
|
287
|
+
type: 'test-bug',
|
|
288
|
+
severity: 'medium',
|
|
289
|
+
agentId: this.agentId,
|
|
290
|
+
module: moduleId,
|
|
291
|
+
description: `Valid submission test failed for form ${formIndex} in module ${moduleId}: ${validSubmitError instanceof Error ? validSubmitError.message.split('\n')[0] : String(validSubmitError)}`,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return findings;
|
|
296
|
+
}
|
|
297
|
+
}
|