@avi770/testteam 1.2.0 → 3.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 +53 -1
- package/README.md +72 -7
- package/agents/15-regression-sentinel.ts +3 -2
- 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/base-agent.ts +8 -0
- package/agents/registry.ts +171 -28
- package/core/__tests__/integration.test.ts +4 -4
- package/core/__tests__/orchestrator.test.ts +17 -16
- package/core/cli.ts +128 -6
- package/core/license.ts +208 -211
- package/core/orchestrator.ts +78 -5
- package/dist/agents/15-regression-sentinel.d.ts +2 -1
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -1
- package/dist/agents/15-regression-sentinel.js +2 -2
- package/dist/agents/15-regression-sentinel.js.map +1 -1
- 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/base-agent.d.ts +3 -1
- package/dist/agents/base-agent.d.ts.map +1 -1
- package/dist/agents/base-agent.js +7 -1
- package/dist/agents/base-agent.js.map +1 -1
- package/dist/agents/registry.d.ts +5 -4
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +170 -29
- package/dist/agents/registry.js.map +1 -1
- package/dist/clients/agent-mvp.d.ts +66 -0
- package/dist/clients/agent-mvp.d.ts.map +1 -0
- package/dist/clients/agent-mvp.js +91 -0
- package/dist/clients/agent-mvp.js.map +1 -0
- package/dist/clients/total-recall.d.ts +37 -0
- package/dist/clients/total-recall.d.ts.map +1 -0
- package/dist/clients/total-recall.js +224 -0
- package/dist/clients/total-recall.js.map +1 -0
- package/dist/core/cli.d.ts +3 -0
- package/dist/core/cli.d.ts.map +1 -1
- package/dist/core/cli.js +122 -6
- package/dist/core/cli.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 +10 -1
- package/dist/core/orchestrator.d.ts.map +1 -1
- package/dist/core/orchestrator.js +66 -5
- package/dist/core/orchestrator.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { Browser, Page, Route } 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
|
+
/** How long to stay offline (ms). */
|
|
8
|
+
const OFFLINE_DURATION_MS = 3_000;
|
|
9
|
+
|
|
10
|
+
export class ErrorRecoveryTesterAgent extends BaseAgent {
|
|
11
|
+
readonly agentId = 51;
|
|
12
|
+
readonly agentName = 'Error Recovery Tester';
|
|
13
|
+
private baseUrl = '';
|
|
14
|
+
|
|
15
|
+
protected async preFlight(): Promise<void> {
|
|
16
|
+
if (!this.config.modules || this.config.modules.length === 0) {
|
|
17
|
+
throw new Error('ErrorRecoveryTesterAgent requires at least one module in config.modules');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
21
|
+
this.baseUrl = env.baseUrl;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected async execute(): Promise<Finding[]> {
|
|
25
|
+
const findings: Finding[] = [];
|
|
26
|
+
|
|
27
|
+
const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
|
|
28
|
+
const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
|
|
29
|
+
const credentials =
|
|
30
|
+
this.config.auth.credentials?.['admin'] ??
|
|
31
|
+
Object.values(this.config.auth.credentials ?? {})[0];
|
|
32
|
+
|
|
33
|
+
const { chromium } = await import('playwright');
|
|
34
|
+
|
|
35
|
+
let browser: Browser | null = null;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
browser = await chromium.launch({ headless: true });
|
|
39
|
+
const page = await browser.newPage();
|
|
40
|
+
|
|
41
|
+
if (credentials) {
|
|
42
|
+
try {
|
|
43
|
+
await login(page, credentials, loginUrl);
|
|
44
|
+
} catch {
|
|
45
|
+
findings.push({
|
|
46
|
+
id: `${this.agentId}-login-failed`,
|
|
47
|
+
type: 'infra-issue',
|
|
48
|
+
severity: 'medium',
|
|
49
|
+
agentId: this.agentId,
|
|
50
|
+
module: 'error-recovery-tester',
|
|
51
|
+
description: 'Login failed — continuing anonymously',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const firstModule = this.config.modules[0];
|
|
57
|
+
const fullUrl = firstModule.route.startsWith('http')
|
|
58
|
+
? firstModule.route
|
|
59
|
+
: `${this.baseUrl}${firstModule.route}`;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
|
|
63
|
+
} catch {
|
|
64
|
+
findings.push({
|
|
65
|
+
id: `${this.agentId}-nav-failed`,
|
|
66
|
+
type: 'test-bug',
|
|
67
|
+
severity: 'medium',
|
|
68
|
+
agentId: this.agentId,
|
|
69
|
+
module: firstModule.id,
|
|
70
|
+
description: `Failed to navigate to ${firstModule.id}`,
|
|
71
|
+
});
|
|
72
|
+
await page.close().catch(() => undefined);
|
|
73
|
+
return findings;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Go offline
|
|
77
|
+
const abortHandler = (route: Route) => route.abort();
|
|
78
|
+
await page.route('**/*', abortHandler);
|
|
79
|
+
|
|
80
|
+
// Wait while offline
|
|
81
|
+
await new Promise(resolve => setTimeout(resolve, OFFLINE_DURATION_MS));
|
|
82
|
+
|
|
83
|
+
// Go back online
|
|
84
|
+
await page.unroute('**/*', abortHandler);
|
|
85
|
+
|
|
86
|
+
// Wait for recovery
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 2_000));
|
|
88
|
+
|
|
89
|
+
// Check if app recovered
|
|
90
|
+
const recoveryFindings = await this.checkRecovery(page, firstModule.id);
|
|
91
|
+
findings.push(...recoveryFindings);
|
|
92
|
+
|
|
93
|
+
await page.close().catch(() => undefined);
|
|
94
|
+
} catch (playwrightError) {
|
|
95
|
+
findings.push({
|
|
96
|
+
id: `${this.agentId}-playwright-failure`,
|
|
97
|
+
type: 'infra-issue',
|
|
98
|
+
severity: 'medium',
|
|
99
|
+
agentId: this.agentId,
|
|
100
|
+
module: 'error-recovery-tester',
|
|
101
|
+
description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
|
|
102
|
+
});
|
|
103
|
+
} finally {
|
|
104
|
+
if (browser) await browser.close().catch(() => undefined);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return findings;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async checkRecovery(page: Page, moduleId: string): Promise<Finding[]> {
|
|
111
|
+
const findings: Finding[] = [];
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const bodyContent = await page.evaluate(() => document.body.innerText?.trim() ?? '');
|
|
115
|
+
|
|
116
|
+
if (bodyContent.length === 0) {
|
|
117
|
+
findings.push({
|
|
118
|
+
id: `${this.agentId}-blank-screen-${moduleId}`,
|
|
119
|
+
type: 'test-bug',
|
|
120
|
+
severity: 'high',
|
|
121
|
+
agentId: this.agentId,
|
|
122
|
+
module: moduleId,
|
|
123
|
+
description: 'App shows blank screen after network recovery — no error recovery UI detected',
|
|
124
|
+
});
|
|
125
|
+
return findings;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for recovery indicators
|
|
129
|
+
const hasRetryButton = await page.locator('button:has-text("Retry"), button:has-text("Reload"), button:has-text("Try Again")').count();
|
|
130
|
+
const hasErrorMessage = await page.locator('[role="alert"], .error, .offline').count();
|
|
131
|
+
|
|
132
|
+
if (hasRetryButton > 0 || hasErrorMessage > 0) {
|
|
133
|
+
findings.push({
|
|
134
|
+
id: `${this.agentId}-recovery-ui-${moduleId}`,
|
|
135
|
+
type: 'test-bug',
|
|
136
|
+
severity: 'low',
|
|
137
|
+
agentId: this.agentId,
|
|
138
|
+
module: moduleId,
|
|
139
|
+
description: 'App shows recovery/error UI after network restoration — manual reload may be needed',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
findings.push({
|
|
144
|
+
id: `${this.agentId}-recovery-check-failed-${moduleId}`,
|
|
145
|
+
type: 'test-bug',
|
|
146
|
+
severity: 'medium',
|
|
147
|
+
agentId: this.agentId,
|
|
148
|
+
module: moduleId,
|
|
149
|
+
description: 'Failed to check recovery state after network restoration',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return findings;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { Browser, Page, Route } 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
|
+
export class OfflineModeTesterAgent extends BaseAgent {
|
|
8
|
+
readonly agentId = 52;
|
|
9
|
+
readonly agentName = 'Offline Mode Tester';
|
|
10
|
+
private baseUrl = '';
|
|
11
|
+
|
|
12
|
+
protected async preFlight(): Promise<void> {
|
|
13
|
+
if (!this.config.modules || this.config.modules.length === 0) {
|
|
14
|
+
throw new Error('OfflineModeTesterAgent requires at least one module in config.modules');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
18
|
+
this.baseUrl = env.baseUrl;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected async execute(): Promise<Finding[]> {
|
|
22
|
+
const findings: Finding[] = [];
|
|
23
|
+
|
|
24
|
+
const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
|
|
25
|
+
const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
|
|
26
|
+
const credentials =
|
|
27
|
+
this.config.auth.credentials?.['admin'] ??
|
|
28
|
+
Object.values(this.config.auth.credentials ?? {})[0];
|
|
29
|
+
|
|
30
|
+
const { chromium } = await import('playwright');
|
|
31
|
+
|
|
32
|
+
let browser: Browser | null = null;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
browser = await chromium.launch({ headless: true });
|
|
36
|
+
const page = await browser.newPage();
|
|
37
|
+
|
|
38
|
+
if (credentials) {
|
|
39
|
+
try {
|
|
40
|
+
await login(page, credentials, loginUrl);
|
|
41
|
+
} catch {
|
|
42
|
+
findings.push({
|
|
43
|
+
id: `${this.agentId}-login-failed`,
|
|
44
|
+
type: 'infra-issue',
|
|
45
|
+
severity: 'medium',
|
|
46
|
+
agentId: this.agentId,
|
|
47
|
+
module: 'offline-mode-tester',
|
|
48
|
+
description: 'Login failed — continuing anonymously',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Load first module (content should be cached)
|
|
54
|
+
const firstModule = this.config.modules[0];
|
|
55
|
+
const firstUrl = firstModule.route.startsWith('http')
|
|
56
|
+
? firstModule.route
|
|
57
|
+
: `${this.baseUrl}${firstModule.route}`;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await page.goto(firstUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
|
|
61
|
+
} catch {
|
|
62
|
+
findings.push({
|
|
63
|
+
id: `${this.agentId}-nav-failed`,
|
|
64
|
+
type: 'test-bug',
|
|
65
|
+
severity: 'medium',
|
|
66
|
+
agentId: this.agentId,
|
|
67
|
+
module: firstModule.id,
|
|
68
|
+
description: `Failed to navigate to ${firstModule.id}`,
|
|
69
|
+
});
|
|
70
|
+
await page.close().catch(() => undefined);
|
|
71
|
+
return findings;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check for service worker
|
|
75
|
+
const hasServiceWorker = await this.checkServiceWorker(page);
|
|
76
|
+
if (!hasServiceWorker) {
|
|
77
|
+
findings.push({
|
|
78
|
+
id: `${this.agentId}-no-service-worker`,
|
|
79
|
+
type: 'test-bug',
|
|
80
|
+
severity: 'low',
|
|
81
|
+
agentId: this.agentId,
|
|
82
|
+
module: 'offline-mode-tester',
|
|
83
|
+
description: 'No service worker controller detected — offline caching likely not available',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Go offline
|
|
88
|
+
const abortHandler = (route: Route) => route.abort();
|
|
89
|
+
await page.route('**/*', abortHandler);
|
|
90
|
+
|
|
91
|
+
// Try navigating to second module while offline
|
|
92
|
+
if (this.config.modules.length >= 2) {
|
|
93
|
+
const secondModule = this.config.modules[1];
|
|
94
|
+
const secondUrl = secondModule.route.startsWith('http')
|
|
95
|
+
? secondModule.route
|
|
96
|
+
: `${this.baseUrl}${secondModule.route}`;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await page.goto(secondUrl, { waitUntil: 'domcontentloaded', timeout: 5_000 });
|
|
100
|
+
} catch {
|
|
101
|
+
// Navigation failure expected offline
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check for offline content
|
|
105
|
+
const offlineFindings = await this.checkOfflineContent(page, secondModule.id);
|
|
106
|
+
findings.push(...offlineFindings);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Restore online
|
|
110
|
+
await page.unroute('**/*', abortHandler);
|
|
111
|
+
|
|
112
|
+
await page.close().catch(() => undefined);
|
|
113
|
+
} catch (playwrightError) {
|
|
114
|
+
findings.push({
|
|
115
|
+
id: `${this.agentId}-playwright-failure`,
|
|
116
|
+
type: 'infra-issue',
|
|
117
|
+
severity: 'medium',
|
|
118
|
+
agentId: this.agentId,
|
|
119
|
+
module: 'offline-mode-tester',
|
|
120
|
+
description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
|
|
121
|
+
});
|
|
122
|
+
} finally {
|
|
123
|
+
if (browser) await browser.close().catch(() => undefined);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return findings;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async checkServiceWorker(page: Page): Promise<boolean> {
|
|
130
|
+
try {
|
|
131
|
+
return await page.evaluate(() => Boolean(navigator.serviceWorker?.controller));
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private async checkOfflineContent(page: Page, moduleId: string): Promise<Finding[]> {
|
|
138
|
+
const findings: Finding[] = [];
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const bodyContent = await page.evaluate(() => document.body.innerText?.trim() ?? '');
|
|
142
|
+
|
|
143
|
+
if (bodyContent.length === 0) {
|
|
144
|
+
findings.push({
|
|
145
|
+
id: `${this.agentId}-blank-offline-${moduleId}`,
|
|
146
|
+
type: 'test-bug',
|
|
147
|
+
severity: 'medium',
|
|
148
|
+
agentId: this.agentId,
|
|
149
|
+
module: moduleId,
|
|
150
|
+
description: `Blank page when navigating to ${moduleId} while offline — no cached content or offline fallback`,
|
|
151
|
+
});
|
|
152
|
+
return findings;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for explicit offline message
|
|
156
|
+
const lowerContent = bodyContent.toLowerCase();
|
|
157
|
+
if (lowerContent.includes('offline') || lowerContent.includes('no connection') || lowerContent.includes('network')) {
|
|
158
|
+
findings.push({
|
|
159
|
+
id: `${this.agentId}-offline-message-${moduleId}`,
|
|
160
|
+
type: 'test-bug',
|
|
161
|
+
severity: 'low',
|
|
162
|
+
agentId: this.agentId,
|
|
163
|
+
module: moduleId,
|
|
164
|
+
description: `Offline message displayed for ${moduleId} — app handles offline gracefully`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
findings.push({
|
|
169
|
+
id: `${this.agentId}-offline-check-failed-${moduleId}`,
|
|
170
|
+
type: 'test-bug',
|
|
171
|
+
severity: 'medium',
|
|
172
|
+
agentId: this.agentId,
|
|
173
|
+
module: moduleId,
|
|
174
|
+
description: `Failed to evaluate page content while offline for ${moduleId}`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return findings;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Browser, Route } from 'playwright';
|
|
2
|
+
import type { Finding } from '../core/types';
|
|
3
|
+
import { BaseAgent } from './base-agent';
|
|
4
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
5
|
+
|
|
6
|
+
export class GracefulDegradationTesterAgent extends BaseAgent {
|
|
7
|
+
readonly agentId = 53;
|
|
8
|
+
readonly agentName = 'Graceful Degradation Tester';
|
|
9
|
+
private baseUrl = '';
|
|
10
|
+
|
|
11
|
+
protected async preFlight(): Promise<void> {
|
|
12
|
+
if (!this.config.modules || this.config.modules.length === 0) {
|
|
13
|
+
throw new Error('GracefulDegradationTesterAgent requires at least one module in config.modules');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
17
|
+
this.baseUrl = env.baseUrl;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected async execute(): Promise<Finding[]> {
|
|
21
|
+
const findings: Finding[] = [];
|
|
22
|
+
|
|
23
|
+
const { chromium } = await import('playwright');
|
|
24
|
+
|
|
25
|
+
let browser: Browser | null = null;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
browser = await chromium.launch({ headless: true });
|
|
29
|
+
|
|
30
|
+
const firstModule = this.config.modules[0];
|
|
31
|
+
const fullUrl = firstModule.route.startsWith('http')
|
|
32
|
+
? firstModule.route
|
|
33
|
+
: `${this.baseUrl}${firstModule.route}`;
|
|
34
|
+
|
|
35
|
+
// Test 1: Disable JavaScript — check for SSR
|
|
36
|
+
const noJsFindings = await this.testNoJavaScript(browser, fullUrl, firstModule.id);
|
|
37
|
+
findings.push(...noJsFindings);
|
|
38
|
+
|
|
39
|
+
// Test 2: Block images — check alt text
|
|
40
|
+
const noImgFindings = await this.testNoImages(browser, fullUrl, firstModule.id);
|
|
41
|
+
findings.push(...noImgFindings);
|
|
42
|
+
} catch (playwrightError) {
|
|
43
|
+
findings.push({
|
|
44
|
+
id: `${this.agentId}-playwright-failure`,
|
|
45
|
+
type: 'infra-issue',
|
|
46
|
+
severity: 'medium',
|
|
47
|
+
agentId: this.agentId,
|
|
48
|
+
module: 'graceful-degradation-tester',
|
|
49
|
+
description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
|
|
50
|
+
});
|
|
51
|
+
} finally {
|
|
52
|
+
if (browser) await browser.close().catch(() => undefined);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return findings;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async testNoJavaScript(
|
|
59
|
+
browser: Browser,
|
|
60
|
+
url: string,
|
|
61
|
+
moduleId: string,
|
|
62
|
+
): Promise<Finding[]> {
|
|
63
|
+
const findings: Finding[] = [];
|
|
64
|
+
|
|
65
|
+
const context = await browser.newContext({ javaScriptEnabled: false });
|
|
66
|
+
const page = await context.newPage();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 10_000 });
|
|
70
|
+
|
|
71
|
+
const bodyContent = await page.evaluate(() => document.body.innerText?.trim() ?? '');
|
|
72
|
+
|
|
73
|
+
if (bodyContent.length === 0) {
|
|
74
|
+
findings.push({
|
|
75
|
+
id: `${this.agentId}-no-ssr-${moduleId}`,
|
|
76
|
+
type: 'test-bug',
|
|
77
|
+
severity: 'medium',
|
|
78
|
+
agentId: this.agentId,
|
|
79
|
+
module: moduleId,
|
|
80
|
+
description: `No content renders with JavaScript disabled — SSR/static fallback not available for ${moduleId}`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Navigation may fail without JS — that itself is a finding
|
|
85
|
+
findings.push({
|
|
86
|
+
id: `${this.agentId}-no-js-nav-failed-${moduleId}`,
|
|
87
|
+
type: 'test-bug',
|
|
88
|
+
severity: 'medium',
|
|
89
|
+
agentId: this.agentId,
|
|
90
|
+
module: moduleId,
|
|
91
|
+
description: `Page failed to load with JavaScript disabled for ${moduleId}`,
|
|
92
|
+
});
|
|
93
|
+
} finally {
|
|
94
|
+
await page.close().catch(() => undefined);
|
|
95
|
+
await context.close().catch(() => undefined);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return findings;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async testNoImages(
|
|
102
|
+
browser: Browser,
|
|
103
|
+
url: string,
|
|
104
|
+
moduleId: string,
|
|
105
|
+
): Promise<Finding[]> {
|
|
106
|
+
const findings: Finding[] = [];
|
|
107
|
+
|
|
108
|
+
const context = await browser.newContext();
|
|
109
|
+
const page = await context.newPage();
|
|
110
|
+
|
|
111
|
+
// Block all image requests
|
|
112
|
+
const imageBlocker = (route: Route) => route.abort();
|
|
113
|
+
await page.route('**/*.{png,jpg,jpeg,gif,webp,svg,ico}', imageBlocker);
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 10_000 });
|
|
117
|
+
|
|
118
|
+
// Check alt text on img elements
|
|
119
|
+
const missingAltCount = await page.evaluate(() => {
|
|
120
|
+
const images = document.querySelectorAll('img');
|
|
121
|
+
let missing = 0;
|
|
122
|
+
for (const img of images) {
|
|
123
|
+
if (!img.alt || img.alt.trim().length === 0) {
|
|
124
|
+
missing++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return missing;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (missingAltCount > 0) {
|
|
131
|
+
findings.push({
|
|
132
|
+
id: `${this.agentId}-missing-alt-${moduleId}`,
|
|
133
|
+
type: 'test-bug',
|
|
134
|
+
severity: 'medium',
|
|
135
|
+
agentId: this.agentId,
|
|
136
|
+
module: moduleId,
|
|
137
|
+
description: `${missingAltCount} image(s) missing alt text in ${moduleId}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
findings.push({
|
|
142
|
+
id: `${this.agentId}-img-test-failed-${moduleId}`,
|
|
143
|
+
type: 'test-bug',
|
|
144
|
+
severity: 'low',
|
|
145
|
+
agentId: this.agentId,
|
|
146
|
+
module: moduleId,
|
|
147
|
+
description: `Failed to test image degradation for ${moduleId}`,
|
|
148
|
+
});
|
|
149
|
+
} finally {
|
|
150
|
+
await page.close().catch(() => undefined);
|
|
151
|
+
await context.close().catch(() => undefined);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return findings;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { Browser } 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
|
+
export class WebSocketTesterAgent extends BaseAgent {
|
|
8
|
+
readonly agentId = 54;
|
|
9
|
+
readonly agentName = 'WebSocket Tester';
|
|
10
|
+
private baseUrl = '';
|
|
11
|
+
|
|
12
|
+
protected async preFlight(): Promise<void> {
|
|
13
|
+
if (!this.config.modules || this.config.modules.length === 0) {
|
|
14
|
+
throw new Error('WebSocketTesterAgent requires at least one module in config.modules');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
18
|
+
this.baseUrl = env.baseUrl;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected async execute(): Promise<Finding[]> {
|
|
22
|
+
const findings: Finding[] = [];
|
|
23
|
+
|
|
24
|
+
const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
|
|
25
|
+
const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
|
|
26
|
+
const credentials =
|
|
27
|
+
this.config.auth.credentials?.['admin'] ??
|
|
28
|
+
Object.values(this.config.auth.credentials ?? {})[0];
|
|
29
|
+
|
|
30
|
+
// Check config for WS integrations
|
|
31
|
+
const hasWsConfig = this.config.integrations.some(
|
|
32
|
+
i => i.name.toLowerCase().includes('websocket') || i.name.toLowerCase().includes('ws') || i.name.toLowerCase().includes('socket'),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const { chromium } = await import('playwright');
|
|
36
|
+
|
|
37
|
+
let browser: Browser | null = null;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
browser = await chromium.launch({ headless: true });
|
|
41
|
+
const page = await browser.newPage();
|
|
42
|
+
|
|
43
|
+
if (credentials) {
|
|
44
|
+
try {
|
|
45
|
+
await login(page, credentials, loginUrl);
|
|
46
|
+
} catch {
|
|
47
|
+
findings.push({
|
|
48
|
+
id: `${this.agentId}-login-failed`,
|
|
49
|
+
type: 'infra-issue',
|
|
50
|
+
severity: 'medium',
|
|
51
|
+
agentId: this.agentId,
|
|
52
|
+
module: 'websocket-tester',
|
|
53
|
+
description: 'Login failed — continuing anonymously',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Listen for WebSocket connections
|
|
59
|
+
const wsConnections: { url: string; error: boolean }[] = [];
|
|
60
|
+
|
|
61
|
+
page.on('websocket', ws => {
|
|
62
|
+
const entry = { url: ws.url(), error: false };
|
|
63
|
+
wsConnections.push(entry);
|
|
64
|
+
|
|
65
|
+
ws.on('socketerror', () => {
|
|
66
|
+
entry.error = true;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
ws.on('close', () => {
|
|
70
|
+
// Track close events if needed
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Navigate through modules
|
|
75
|
+
for (const mod of this.config.modules) {
|
|
76
|
+
const fullUrl = mod.route.startsWith('http')
|
|
77
|
+
? mod.route
|
|
78
|
+
: `${this.baseUrl}${mod.route}`;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
|
|
82
|
+
// Wait a moment for WS connections to establish
|
|
83
|
+
await new Promise(resolve => setTimeout(resolve, 2_000));
|
|
84
|
+
} catch {
|
|
85
|
+
// Navigation failure — non-critical for WS testing
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Report findings
|
|
90
|
+
if (wsConnections.length === 0 && hasWsConfig) {
|
|
91
|
+
findings.push({
|
|
92
|
+
id: `${this.agentId}-ws-configured-no-connection`,
|
|
93
|
+
type: 'test-bug',
|
|
94
|
+
severity: 'medium',
|
|
95
|
+
agentId: this.agentId,
|
|
96
|
+
module: 'websocket-tester',
|
|
97
|
+
description: 'WebSocket integration configured but no WS connections detected',
|
|
98
|
+
});
|
|
99
|
+
} else if (wsConnections.length === 0) {
|
|
100
|
+
findings.push({
|
|
101
|
+
id: `${this.agentId}-no-ws-connections`,
|
|
102
|
+
type: 'test-bug',
|
|
103
|
+
severity: 'low',
|
|
104
|
+
agentId: this.agentId,
|
|
105
|
+
module: 'websocket-tester',
|
|
106
|
+
description: 'No WebSocket connections detected during navigation',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check for failed connections
|
|
111
|
+
const failedWs = wsConnections.filter(ws => ws.error);
|
|
112
|
+
if (failedWs.length > 0) {
|
|
113
|
+
for (const ws of failedWs) {
|
|
114
|
+
findings.push({
|
|
115
|
+
id: `${this.agentId}-ws-error-${wsConnections.indexOf(ws)}`,
|
|
116
|
+
type: 'test-bug',
|
|
117
|
+
severity: 'medium',
|
|
118
|
+
agentId: this.agentId,
|
|
119
|
+
module: 'websocket-tester',
|
|
120
|
+
description: `WebSocket connection failed: ${ws.url}`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Report successful connections
|
|
126
|
+
const successfulWs = wsConnections.filter(ws => !ws.error);
|
|
127
|
+
if (successfulWs.length > 0) {
|
|
128
|
+
this.addEvidence({
|
|
129
|
+
type: 'log',
|
|
130
|
+
path: '',
|
|
131
|
+
description: `Detected ${successfulWs.length} successful WebSocket connection(s): ${successfulWs.map(ws => ws.url).join(', ')}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await page.close().catch(() => undefined);
|
|
136
|
+
} catch (playwrightError) {
|
|
137
|
+
findings.push({
|
|
138
|
+
id: `${this.agentId}-playwright-failure`,
|
|
139
|
+
type: 'infra-issue',
|
|
140
|
+
severity: 'medium',
|
|
141
|
+
agentId: this.agentId,
|
|
142
|
+
module: 'websocket-tester',
|
|
143
|
+
description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
|
|
144
|
+
});
|
|
145
|
+
} finally {
|
|
146
|
+
if (browser) await browser.close().catch(() => undefined);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return findings;
|
|
150
|
+
}
|
|
151
|
+
}
|