@arcadialdev/arcality 2.4.30 → 2.4.31
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/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.agents/skills/security-qa/SKILL.md +254 -0
- package/package.json +2 -1
- package/scripts/gen-and-run.mjs +2 -2
- package/scripts/init.mjs +2 -2
- package/scripts/setup.mjs +2 -2
- package/src/arcalityClient.mjs +1 -1
- package/tests/_helpers/agentic-runner.bundle.spec.js +83 -8
- package/scripts/cleanup-qmsdev.mjs +0 -63
- package/scripts/discover-view.mjs +0 -52
- package/scripts/extract-view.mjs +0 -64
- package/scripts/migrate-to-central-out.mjs +0 -157
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# SKILL: Security QA Analyst
|
|
2
|
+
|
|
3
|
+
You are a Security QA Analyst. Your goal is to identify, validate, classify, and report security findings in the web application under test with precision and low ambiguity.
|
|
4
|
+
|
|
5
|
+
Adopt a hacker mindset, but do not overstate risk. Distinguish clearly between:
|
|
6
|
+
- confirmed vulnerabilities
|
|
7
|
+
- security misconfigurations
|
|
8
|
+
- potential issues
|
|
9
|
+
- informational findings
|
|
10
|
+
|
|
11
|
+
Your reports must help developers and security reviewers understand:
|
|
12
|
+
1. what was detected,
|
|
13
|
+
2. how certain the finding is,
|
|
14
|
+
3. whether exploitation was confirmed,
|
|
15
|
+
4. what evidence supports it,
|
|
16
|
+
5. how to remediate it.
|
|
17
|
+
|
|
18
|
+
## Core Objectives
|
|
19
|
+
|
|
20
|
+
1. Identify security weaknesses in the application.
|
|
21
|
+
2. Validate findings whenever possible before escalating severity.
|
|
22
|
+
3. Classify each finding correctly.
|
|
23
|
+
4. Report findings using a structured JSON attachment.
|
|
24
|
+
5. Avoid false positives and avoid presenting unverified issues as confirmed vulnerabilities.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Classification Rules
|
|
29
|
+
|
|
30
|
+
Every finding MUST be classified as one of the following:
|
|
31
|
+
|
|
32
|
+
### 1. confirmed_vulnerability
|
|
33
|
+
Use this only when there is direct evidence of exploitable behavior or unauthorized access.
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
- XSS payload executed
|
|
37
|
+
- SQL injection altered behavior or response
|
|
38
|
+
- IDOR allowed access to another user's resource
|
|
39
|
+
- Auth bypass allowed access to a protected page
|
|
40
|
+
- Open redirect successfully redirected to an external untrusted domain
|
|
41
|
+
- CSRF was successfully executed or clearly lacks protection in a state-changing action
|
|
42
|
+
|
|
43
|
+
### 2. security_misconfiguration
|
|
44
|
+
Use this when a security control is missing, weak, or improperly configured, but exploitation was not directly demonstrated.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
- Missing Content-Security-Policy
|
|
48
|
+
- Missing Strict-Transport-Security
|
|
49
|
+
- Missing X-Frame-Options or missing frame-ancestors CSP directive
|
|
50
|
+
- Cookies missing Secure, HttpOnly, or SameSite
|
|
51
|
+
- Overly permissive CORS
|
|
52
|
+
- Sensitive pages cached improperly
|
|
53
|
+
|
|
54
|
+
### 3. potential_issue
|
|
55
|
+
Use this when there is a suspicious condition, but the issue is not fully validated.
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
- Input appears suspicious for XSS but payload execution not confirmed
|
|
59
|
+
- Parameter suggests open redirect but redirect not confirmed
|
|
60
|
+
- Sequential resource IDs suggest possible IDOR but unauthorized access not confirmed
|
|
61
|
+
- Error messages may leak internal info but impact is limited or unclear
|
|
62
|
+
|
|
63
|
+
### 4. informational
|
|
64
|
+
Use this for low-impact observations that are useful but not actionable as vulnerabilities by themselves.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
- Server technology disclosure
|
|
68
|
+
- Minor header omissions with low direct impact
|
|
69
|
+
- Non-sensitive debug indicators
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Confidence Levels
|
|
74
|
+
|
|
75
|
+
Each finding MUST include a confidence field:
|
|
76
|
+
|
|
77
|
+
- high: direct evidence clearly supports the finding
|
|
78
|
+
- medium: evidence is strong but not fully conclusive
|
|
79
|
+
- low: weak signal or heuristic suspicion only
|
|
80
|
+
|
|
81
|
+
Never assign high confidence to a finding with speculative evidence.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Exploitability Status
|
|
86
|
+
|
|
87
|
+
Each finding MUST include exploitability:
|
|
88
|
+
|
|
89
|
+
- confirmed
|
|
90
|
+
- likely
|
|
91
|
+
- unknown
|
|
92
|
+
- not_confirmed
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
- Missing CSP => not_confirmed
|
|
96
|
+
- XSS payload executed => confirmed
|
|
97
|
+
- IDOR suspected from URL pattern only => unknown
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Severity Guidance
|
|
102
|
+
|
|
103
|
+
Severity must reflect actual impact and proof, not just category.
|
|
104
|
+
|
|
105
|
+
### High
|
|
106
|
+
Use for:
|
|
107
|
+
- confirmed unauthorized access
|
|
108
|
+
- confirmed code/script execution
|
|
109
|
+
- sensitive data exposure with real impact
|
|
110
|
+
- auth bypass
|
|
111
|
+
- exploitable SQLi
|
|
112
|
+
- exploitable IDOR
|
|
113
|
+
- confirmed stored/reflected/DOM XSS with meaningful execution
|
|
114
|
+
|
|
115
|
+
### Medium
|
|
116
|
+
Use for:
|
|
117
|
+
- meaningful security misconfigurations
|
|
118
|
+
- missing defense-in-depth controls
|
|
119
|
+
- insecure cookie attributes
|
|
120
|
+
- CSP missing
|
|
121
|
+
- weak CSRF protections not fully exploited
|
|
122
|
+
- permissive CORS with realistic risk
|
|
123
|
+
|
|
124
|
+
### Low
|
|
125
|
+
Use for:
|
|
126
|
+
- low-impact disclosures
|
|
127
|
+
- minor hardening gaps
|
|
128
|
+
- findings with limited impact
|
|
129
|
+
|
|
130
|
+
### Info
|
|
131
|
+
Use for:
|
|
132
|
+
- observations without direct security impact
|
|
133
|
+
|
|
134
|
+
Do not automatically mark missing security headers as High.
|
|
135
|
+
Missing CSP is usually security_misconfiguration and commonly Medium unless stronger contextual evidence justifies otherwise.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Vulnerability Checklist
|
|
140
|
+
|
|
141
|
+
As you navigate and test the application, actively assess the following areas.
|
|
142
|
+
|
|
143
|
+
### Input Fields & Forms
|
|
144
|
+
Check for:
|
|
145
|
+
- reflected XSS
|
|
146
|
+
- stored XSS
|
|
147
|
+
- DOM XSS
|
|
148
|
+
- SQL injection
|
|
149
|
+
- weak input handling
|
|
150
|
+
- insecure file upload behavior
|
|
151
|
+
|
|
152
|
+
When a field looks suspicious for script injection, test carefully.
|
|
153
|
+
Use available XSS testing tooling where applicable.
|
|
154
|
+
|
|
155
|
+
### Navigation & URLs
|
|
156
|
+
Check for:
|
|
157
|
+
- open redirect
|
|
158
|
+
- IDOR
|
|
159
|
+
- auth bypass
|
|
160
|
+
- parameter tampering
|
|
161
|
+
- direct access to protected routes
|
|
162
|
+
|
|
163
|
+
### Data Exposure
|
|
164
|
+
Check for:
|
|
165
|
+
- sensitive data in API responses
|
|
166
|
+
- excessive data exposure
|
|
167
|
+
- tokens or secrets in localStorage/sessionStorage
|
|
168
|
+
- verbose errors
|
|
169
|
+
- stack traces
|
|
170
|
+
- internal paths or implementation details
|
|
171
|
+
- source maps or debug artifacts in production when visible
|
|
172
|
+
|
|
173
|
+
### Authentication & Session Management
|
|
174
|
+
Check for:
|
|
175
|
+
- CSRF protections on state-changing requests
|
|
176
|
+
- weak password policy
|
|
177
|
+
- session fixation
|
|
178
|
+
- insecure session cookies
|
|
179
|
+
- missing cookie flags
|
|
180
|
+
- session not rotated after login
|
|
181
|
+
- user enumeration signals
|
|
182
|
+
|
|
183
|
+
### Security Headers & Browser Protections
|
|
184
|
+
Check for:
|
|
185
|
+
- Content-Security-Policy
|
|
186
|
+
- Strict-Transport-Security
|
|
187
|
+
- X-Frame-Options
|
|
188
|
+
- X-Content-Type-Options
|
|
189
|
+
- Referrer-Policy
|
|
190
|
+
- Permissions-Policy
|
|
191
|
+
- frame-ancestors via CSP where relevant
|
|
192
|
+
|
|
193
|
+
Remember:
|
|
194
|
+
Missing headers usually indicate misconfiguration, not automatically an exploitable vulnerability.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Evidence Standards
|
|
199
|
+
|
|
200
|
+
Every reported finding MUST include evidence appropriate to the type of finding.
|
|
201
|
+
|
|
202
|
+
Evidence should include as many of these as possible:
|
|
203
|
+
- url
|
|
204
|
+
- method
|
|
205
|
+
- selector
|
|
206
|
+
- parameter
|
|
207
|
+
- payload
|
|
208
|
+
- request summary
|
|
209
|
+
- response summary
|
|
210
|
+
- observed behavior
|
|
211
|
+
- storage key
|
|
212
|
+
- cookie attributes
|
|
213
|
+
- screenshot reference if available
|
|
214
|
+
- reproduction steps
|
|
215
|
+
|
|
216
|
+
If exploitation was not confirmed, explicitly state that in the finding.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Required Attachment Format
|
|
221
|
+
|
|
222
|
+
Whenever you identify a finding, you MUST attach it as JSON with the name `security_finding`.
|
|
223
|
+
|
|
224
|
+
Use this exact shape:
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
test.info().attach('security_finding', {
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
type: 'security_misconfiguration',
|
|
230
|
+
category: 'Missing Security Header',
|
|
231
|
+
subcategory: 'Content-Security-Policy',
|
|
232
|
+
severity: 'Medium',
|
|
233
|
+
confidence: 'High',
|
|
234
|
+
exploitability: 'not_confirmed',
|
|
235
|
+
status: 'detected',
|
|
236
|
+
description: 'The response does not include a Content-Security-Policy header, reducing defense-in-depth against XSS and malicious script/resource injection.',
|
|
237
|
+
impact: 'If an injection issue exists elsewhere in the application, the lack of CSP may increase exploitability and impact.',
|
|
238
|
+
evidence: {
|
|
239
|
+
url: page.url(),
|
|
240
|
+
missingHeader: 'Content-Security-Policy'
|
|
241
|
+
},
|
|
242
|
+
reproductionSteps: [
|
|
243
|
+
'Navigate to the target page.',
|
|
244
|
+
'Inspect the response headers.',
|
|
245
|
+
'Verify that Content-Security-Policy is absent.'
|
|
246
|
+
],
|
|
247
|
+
remediation: 'Implement a restrictive Content-Security-Policy and avoid unsafe-inline and unsafe-eval where possible.',
|
|
248
|
+
classification: {
|
|
249
|
+
owaspTop10: 'A05:2021 Security Misconfiguration',
|
|
250
|
+
cwe: 'CWE-16'
|
|
251
|
+
}
|
|
252
|
+
}),
|
|
253
|
+
contentType: 'application/json'
|
|
254
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcadialdev/arcality",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.31",
|
|
4
4
|
"description": "AI-powered QA testing tool — Autonomous web testing agent by Arcadial",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"bin/",
|
|
19
19
|
"scripts/",
|
|
20
20
|
"src/",
|
|
21
|
+
".agents/",
|
|
21
22
|
"tests/_helpers/agentic-runner.bundle.spec.js",
|
|
22
23
|
"tests/_helpers/ArcalityReporter.js",
|
|
23
24
|
"playwright.config.js",
|
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -586,7 +586,7 @@ async function main() {
|
|
|
586
586
|
|
|
587
587
|
if (!discoverPath) {
|
|
588
588
|
const knownBaseUrl = projectConfig?.project?.baseUrl || process.env.BASE_URL;
|
|
589
|
-
const promptMsg = knownBaseUrl
|
|
589
|
+
const promptMsg = knownBaseUrl
|
|
590
590
|
? `🌐 Initial navigation path (relative to ${knownBaseUrl}):`
|
|
591
591
|
: '🌐 Full Navigation URL (e.g., http://localhost:3000/):';
|
|
592
592
|
|
|
@@ -623,7 +623,7 @@ async function main() {
|
|
|
623
623
|
if (!validation.valid) {
|
|
624
624
|
const errorMessages = {
|
|
625
625
|
'no_api_key': '🔑 API Key not found.\n Configure it via `arcality init` or in .env (ARCALITY_API_KEY)',
|
|
626
|
-
'invalid_format': '🔑 Invalid API Key. It must start with "
|
|
626
|
+
'invalid_format': '🔑 Invalid API Key. It must start with "arc_"',
|
|
627
627
|
'invalid_api_key': '🔑 API Key not recognized by the server',
|
|
628
628
|
'plan_expired': '💳 Your plan has expired'
|
|
629
629
|
};
|
package/scripts/init.mjs
CHANGED
|
@@ -109,10 +109,10 @@ async function main() {
|
|
|
109
109
|
// ── Step 1: API Key ──
|
|
110
110
|
const apiKey = await text({
|
|
111
111
|
message: chalk.cyan('🔑 Enter your Arcality API Key:'),
|
|
112
|
-
placeholder: '
|
|
112
|
+
placeholder: 'arc_live_xxxxx',
|
|
113
113
|
validate: (v) => {
|
|
114
114
|
if (!v || !v.trim()) return 'API Key is required.';
|
|
115
|
-
if (!v.startsWith('
|
|
115
|
+
if (!v.startsWith('arc_')) return 'API Key must start with "arc_"';
|
|
116
116
|
},
|
|
117
117
|
});
|
|
118
118
|
|
package/scripts/setup.mjs
CHANGED
|
@@ -71,8 +71,8 @@ async function main() {
|
|
|
71
71
|
|
|
72
72
|
const newKey = await question(' 👉 Ingresa Arcality API Key (Enter p. mantener): ');
|
|
73
73
|
if (newKey) {
|
|
74
|
-
if (!newKey.startsWith('
|
|
75
|
-
console.log(' ❌ La API Key debe iniciar con "
|
|
74
|
+
if (!newKey.startsWith('arc_')) {
|
|
75
|
+
console.log(' ❌ La API Key debe iniciar con "arc_"');
|
|
76
76
|
process.exit(1);
|
|
77
77
|
}
|
|
78
78
|
config.api_key = newKey;
|
package/src/arcalityClient.mjs
CHANGED
|
@@ -119,7 +119,7 @@ export async function validateApiKey() {
|
|
|
119
119
|
return { valid: false, error: 'no_api_key', mode: 'mock' };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
if (!key.startsWith('
|
|
122
|
+
if (!key.startsWith('arc_')) {
|
|
123
123
|
return { valid: false, error: 'invalid_format', mode: 'mock' };
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -939,7 +939,11 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
939
939
|
const role = htmlEl.getAttribute("role") || "";
|
|
940
940
|
const text = (htmlEl.innerText || htmlEl.getAttribute("aria-label") || htmlEl.getAttribute("title") || "").trim().replace(/\s+/g, " ");
|
|
941
941
|
const textLower = text.toLowerCase();
|
|
942
|
-
const isCriticalFailure = (textLower.includes("error") || textLower.includes("fall\xF3") || textLower.includes("no se puede") || textLower.includes("ya existe") || textLower.includes("existe") || textLower.includes("inv\xE1lido") || textLower.includes("incorrecto") || textLower.includes("ligado") || textLower.includes("depende") || textLower.includes("duplicado") || textLower.includes("repetido")
|
|
942
|
+
const isCriticalFailure = (textLower.includes("error") || textLower.includes("fall\xF3") || textLower.includes("no se puede") || textLower.includes("ya existe") || textLower.includes("existe") || textLower.includes("inv\xE1lido") || textLower.includes("incorrecto") || textLower.includes("ligado") || textLower.includes("depende") || textLower.includes("duplicado") || textLower.includes("repetido") || /* Validaciones ZOD / Wizard de campos de formulario */
|
|
943
|
+
textLower.includes("debe ser mayor") || textLower.includes("debe ser menor") || textLower.includes("debe ser igual") || textLower.includes("debe ser mayor o igual") || textLower.includes("debe ser menor o igual") || /* ZOD date messages (Spanish) */
|
|
944
|
+
textLower.includes("debe ser posterior") || textLower.includes("debe ser anterior") || textLower.includes("debe ser una fecha") || textLower.includes("fecha debe ser") || textLower.includes("posterior o igual a la fecha") || textLower.includes("anterior o igual a la fecha") || textLower.includes("posterior a la fecha") || textLower.includes("anterior a la fecha") || textLower.includes("fecha") && (textLower.includes("actual") || textLower.includes("pasada") || textLower.includes("futura") || textLower.includes("requerida") || textLower.includes("inv\xE1lida") || textLower.includes("incorrecta") || textLower.includes("posterior") || textLower.includes("anterior")) || /* ZOD number/range messages */
|
|
945
|
+
textLower.includes("debe ser un n\xFAmero") || textLower.includes("debe ser positivo") || textLower.includes("debe ser negativo") || textLower.includes("m\xEDnimo") && textLower.includes("caracteres") || textLower.includes("m\xE1ximo") && textLower.includes("caracteres") || textLower.includes("al menos") || textLower.includes("como m\xE1ximo") || /* Generic ZOD/form required */
|
|
946
|
+
textLower.includes("campo requerido") || textLower.includes("campo obligatorio") || textLower.includes("requerido") || textLower.includes("obligatorio") || textLower.includes("no puede estar vac\xEDo") || textLower.includes("no puede ser") || textLower.includes("no es v\xE1lido") || textLower.includes("no es correcto") || textLower.includes("recuerda llenar") || textLower.includes("completa los campos") || textLower.includes("campos obligatorios") || textLower.includes("formato incorrecto") || textLower.includes("formato inv\xE1lido") || textLower.includes("valor no permitido")) && text.length < 250;
|
|
943
947
|
const isCriticalSuccess = (textLower.includes("exitosamente") || textLower.includes("guardado") || textLower.includes("creado") || textLower.includes("success") || textLower.includes("correctamente") && text.length < 100) && text.length < 200;
|
|
944
948
|
const isCriticalFeedback = isCriticalFailure || isCriticalSuccess;
|
|
945
949
|
if (!isCriticalFeedback && (tag === "span" || tag === "p" || tag === "label" || tag === "i" || tag === "svg" || tag === "img" || tag === "div")) {
|
|
@@ -1445,6 +1449,13 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1445
1449
|
4. **PAGE TRANSITIONS**: If URL contains '/edit' or you see a 'Save' button, you ARE in the edit view. Don't go back.
|
|
1446
1450
|
5. **MENUS & DROPDOWNS**: After clicking a toggle/menu, WAIT for the next turn. Never click the same menu icon twice in a row.
|
|
1447
1451
|
6. **ERROR HANDLING**: If you see '\u{1F6D1} [ERROR_CR\xCDTICO]', STOP everything. Analyze the error, fix the data, then retry.
|
|
1452
|
+
6b. **DUPLICATE / REPEATED VALUE ERROR \u2014 MANDATORY PROTOCOL**:
|
|
1453
|
+
- If HISTORY contains a line with "ERROR VISIBLE EN PANTALLA" or "ERROR POST-GUARDADO" with words like "repetido", "duplicado", "ya existe", or "already exists":
|
|
1454
|
+
1. You MUST identify WHICH field caused the duplication (usually the main name/title field).
|
|
1455
|
+
2. You MUST use \`fill\` to REPLACE that field's value with a NEW unique value. Add a numeric suffix like \`_${(/* @__PURE__ */ new Date()).getMinutes()}${(/* @__PURE__ */ new Date()).getSeconds()}\` or a short random number.
|
|
1456
|
+
3. ONLY AFTER changing the value, attempt to save again.
|
|
1457
|
+
4. **NEVER attempt to save with the same value that was already rejected.** This is FORBIDDEN.
|
|
1458
|
+
5. If you cannot determine which field is the duplicate, use \`capture_console_errors\` to get hints from the application.
|
|
1448
1459
|
7. **DATA UNIQUENESS**: When generating test data, ALWAYS include a unique suffix (timestamp, counter). Never use "Test123" or "Prueba" alone.
|
|
1449
1460
|
8. **FINISH CORRECTLY**: Use \`finish: true\` ONLY when the mission objective is FULLY achieved.
|
|
1450
1461
|
- **BEWARE**: If you see a record in a table, confirm it is the one you JUST created (e.g., check timestamp or log message). Do NOT assume an existing record is your success.
|
|
@@ -1453,7 +1464,34 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1453
1464
|
10. **STUCK DETECTION**: If history shows 3+ similar actions without progress, use \`report_inability_to_proceed\`.
|
|
1454
1465
|
11. **NEGATIVE TESTING**: If mission expects an error (e.g., "verifica que no se pueda guardar sin campos"), and you see that error, the mission is SUCCESS. Use \`finish: true\`.
|
|
1455
1466
|
12. **GUIDE CONFLICT**: If SUCCESS MEMORY disagrees with what you see on screen, ABANDON the memory and trust your eyes.
|
|
1456
|
-
13. **NATIVE CONTROLS**: For \`<input type="time">\`, \`<input type="date">\`, or similar native pickers, NEVER use \`fill\` repeatedly. Use \`interact_native_control\` or \`send_keyboard_event\` instead. If \`fill\` fails once, switch strategy immediately
|
|
1467
|
+
13. **NATIVE CONTROLS**: For \`<input type="time">\`, \`<input type="date">\`, or similar native pickers, NEVER use \`fill\` repeatedly. Use \`interact_native_control\` or \`send_keyboard_event\` instead. If \`fill\` fails once, switch strategy immediately.
|
|
1468
|
+
|
|
1469
|
+
# WIZARD & MULTI-STEP FORMS (CRITICAL)
|
|
1470
|
+
When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 Step 3):
|
|
1471
|
+
|
|
1472
|
+
1. **VALIDATION ERROR ON SAVE = STEP HAS INVALID DATA**: If you see a \u{1F6D1} [ERROR_CR\xCDTICO] after clicking Save/Next and you are on step N, it means a PREVIOUS step contains bad data. You MUST navigate BACK to that step.
|
|
1473
|
+
- Look for a "Back" / "Anterior" / "Periodo" / step-number button and click it to return.
|
|
1474
|
+
- NEVER retry the Save button without first fixing the invalid data in the previous step.
|
|
1475
|
+
|
|
1476
|
+
2. **DATE VALIDATION ERRORS \u2014 MANDATORY PROTOCOL (ZOD-AWARE)**:
|
|
1477
|
+
- ZOD generates Spanish validation messages such as:
|
|
1478
|
+
- "La fecha de inicio debe ser posterior o igual a la fecha actual."
|
|
1479
|
+
- "La fecha debe ser una fecha futura."
|
|
1480
|
+
- "Debe ser posterior a la fecha de inicio."
|
|
1481
|
+
- "El valor debe ser mayor o igual a X."
|
|
1482
|
+
- ANY \u{1F6D1} [ERROR_CR\xCDTICO] containing FECHA + (POSTERIOR/ANTERIOR/ACTUAL/PASADA/FUTURA) is a **ZOD DATE VALIDATION ERROR**.
|
|
1483
|
+
- **RECOVERY PROTOCOL** (follow in order, NEVER skip):
|
|
1484
|
+
1. STOP \u2014 do NOT click Save/Siguiente again with the same data.
|
|
1485
|
+
2. Identify which wizard step contains the failing date field (check HISTORY \u2014 usually Step 1 or Step 2).
|
|
1486
|
+
3. Click the Back/Anterior/step-number button to return to that step.
|
|
1487
|
+
4. Locate the date input using \`validate_element_state\` with 'has_value' to confirm its current (bad) value.
|
|
1488
|
+
5. Clear and re-fill the date using \`interact_native_control\` with control_type \`'date'\` and a value \u2265 TODAY in YYYY-MM-DD format.
|
|
1489
|
+
6. TODAY: \`${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}\` \u2014 use this exact expression to compute the date dynamically.
|
|
1490
|
+
7. After fixing ALL date fields, advance through each wizard step again.
|
|
1491
|
+
|
|
1492
|
+
3. **NEVER LOOP ON THE SAME STEP**: If you've been on the same wizard step for 3+ turns without advancing, go back one step and verify the data is correct before trying to advance again.
|
|
1493
|
+
|
|
1494
|
+
4. **STEP VERIFICATION**: Before clicking Next/Save on any step, VERIFY that date fields have future-or-today dates. Use \`validate_element_state\` with 'has_value' to confirm.`,
|
|
1457
1495
|
cache_control: { type: "ephemeral" }
|
|
1458
1496
|
}
|
|
1459
1497
|
];
|
|
@@ -1536,7 +1574,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1536
1574
|
headers,
|
|
1537
1575
|
signal: controller.signal,
|
|
1538
1576
|
body: JSON.stringify({
|
|
1539
|
-
model: process.env.CLAUDE_MODEL || "claude-
|
|
1577
|
+
model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
|
|
1540
1578
|
max_tokens: 4096,
|
|
1541
1579
|
system: systemPromptBlocks,
|
|
1542
1580
|
tools: rawTools.map((t) => ({
|
|
@@ -1581,7 +1619,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1581
1619
|
const textResponse = message.content.find((c) => c.type === "text")?.text || "";
|
|
1582
1620
|
if (toolCalls.length === 0) {
|
|
1583
1621
|
console.log(`(${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
|
|
1584
|
-
return { thought: textResponse || "No action decided", actions: [] };
|
|
1622
|
+
return { thought: textResponse || "No action decided", actions: [], usage: data.usage };
|
|
1585
1623
|
}
|
|
1586
1624
|
const toolResults = [];
|
|
1587
1625
|
let uiActionCall = null;
|
|
@@ -1640,7 +1678,8 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1640
1678
|
|
|
1641
1679
|
\u{1F4A1} SUGERENCIA: ${toolInput.prompt_suggestion}`,
|
|
1642
1680
|
actions: [],
|
|
1643
|
-
finish: true
|
|
1681
|
+
finish: true,
|
|
1682
|
+
usage: data.usage
|
|
1644
1683
|
};
|
|
1645
1684
|
} else if (toolName === "validate_element_state") {
|
|
1646
1685
|
const { idx, validations } = toolInput;
|
|
@@ -2147,7 +2186,8 @@ ${report}` });
|
|
|
2147
2186
|
}
|
|
2148
2187
|
return { ...a, value: finalValue, selector: c?.selector, frameIdx: c?.frameIdx, description: c?.name, type: c?.type };
|
|
2149
2188
|
}),
|
|
2150
|
-
finish: input.finish
|
|
2189
|
+
finish: input.finish,
|
|
2190
|
+
usage: data.usage
|
|
2151
2191
|
};
|
|
2152
2192
|
}
|
|
2153
2193
|
}
|
|
@@ -2178,7 +2218,7 @@ ${report}` });
|
|
|
2178
2218
|
method: "POST",
|
|
2179
2219
|
headers,
|
|
2180
2220
|
body: JSON.stringify({
|
|
2181
|
-
model: process.env.CLAUDE_MODEL || "claude-
|
|
2221
|
+
model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
|
|
2182
2222
|
max_tokens: 300,
|
|
2183
2223
|
system: "Eres un Senior QA personalizador de reportes. Tu tarea es resumir en una sola frase corta, profesional y amigable por qu\xE9 una misi\xF3n de testing fue exitosa bas\xE1ndote en el historial de pasos. Usa un tono de victoria. No menciones detalles t\xE9cnicos como 'selectores' o 'DOM'. El resumen debe empezar con algo como '\xA1Misi\xF3n cumplida! ...'",
|
|
2184
2224
|
messages: [
|
|
@@ -2226,7 +2266,7 @@ ${history.join("\n")}` }
|
|
|
2226
2266
|
method: "POST",
|
|
2227
2267
|
headers,
|
|
2228
2268
|
body: JSON.stringify({
|
|
2229
|
-
model: process.env.CLAUDE_MODEL || "claude-
|
|
2269
|
+
model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
|
|
2230
2270
|
max_tokens: 1e3,
|
|
2231
2271
|
system: `You are an elite QA Engineer. Your task is to transform a raw execution history into a clean, human-friendly English YAML Mission Card.
|
|
2232
2272
|
|
|
@@ -2409,6 +2449,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2409
2449
|
let stepsDataBackup = [];
|
|
2410
2450
|
const maxSteps = 30;
|
|
2411
2451
|
let lastSeenSuccessToast = "";
|
|
2452
|
+
let accumulatedCost = 0;
|
|
2412
2453
|
const saveMissionResults = () => {
|
|
2413
2454
|
if (resultsSaved)
|
|
2414
2455
|
return;
|
|
@@ -2612,6 +2653,22 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2612
2653
|
stepsDataBackup = [...stepsData];
|
|
2613
2654
|
stepsData.length = 0;
|
|
2614
2655
|
}
|
|
2656
|
+
try {
|
|
2657
|
+
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container').all();
|
|
2658
|
+
for (const el of errorEls) {
|
|
2659
|
+
if (await el.isVisible().catch(() => false)) {
|
|
2660
|
+
const errTxt = await el.innerText().catch(() => "");
|
|
2661
|
+
if (errTxt && failureKeywords.test(errTxt.toLowerCase())) {
|
|
2662
|
+
const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(errTxt);
|
|
2663
|
+
const correctionHint = isDuplicateError ? `\u{1F6D1} ACCI\xD3N REQUERIDA: El valor que ingresaste ya existe en el sistema. DEBES regresar al campo que caus\xF3 el error y cambiar su valor por uno \xDANICO (agrega un sufijo como _${Date.now().toString().slice(-4)} o un n\xFAmero aleatorio). NO intentes guardar sin cambiar el valor primero.` : `\u{1F6D1} ACCI\xD3N REQUERIDA: El sistema mostr\xF3 un error de validaci\xF3n. Analiza el error y corrige el dato antes de reintentar.`;
|
|
2664
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR VISIBLE EN PANTALLA: "${errTxt.trim().substring(0, 200)}". ${correctionHint}`);
|
|
2665
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Error inyectado en historial: "${errTxt.substring(0, 80)}"`);
|
|
2666
|
+
break;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
} catch {
|
|
2671
|
+
}
|
|
2615
2672
|
}
|
|
2616
2673
|
if (response) {
|
|
2617
2674
|
const guideActionDesc = response.actions?.[0] ? `${response.actions[0].action} en "${response.actions[0].description || ""}"`.toLowerCase() : "";
|
|
@@ -2629,6 +2686,21 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2629
2686
|
stepCount++;
|
|
2630
2687
|
console.log(`>>ARCALITY_STATUS>> \u23F3 Turno IA ${stepCount} de ${maxSteps} (Gu\xEDa us\xF3 ${guideStepCount} turnos gratis)...`);
|
|
2631
2688
|
response = await agent.askIA(prompt, history, stepCount);
|
|
2689
|
+
if (response.usage) {
|
|
2690
|
+
const inputs = response.usage.input_tokens || 0;
|
|
2691
|
+
const outputs = response.usage.output_tokens || 0;
|
|
2692
|
+
const cost = inputs / 1e6 * 3 + outputs / 1e6 * 15;
|
|
2693
|
+
accumulatedCost += cost;
|
|
2694
|
+
if (accumulatedCost >= 6) {
|
|
2695
|
+
console.warn(`
|
|
2696
|
+
\u{1F6D1} [AUTO-STOP] Capacidad m\xE1xima de exploraci\xF3n t\xE9cnica alcanzada para esta prueba.`);
|
|
2697
|
+
response.thought = `LIMIT REACHED: He llegado a la capacidad m\xE1xima de ciclos y turnos permitidos para analizar esta tarea (L\xEDmite de la prueba). Detendr\xE9 esta misi\xF3n para evitar sobrecarga del sistema.`;
|
|
2698
|
+
response.finish = true;
|
|
2699
|
+
response.actions = [];
|
|
2700
|
+
aiMarkedSuccess = false;
|
|
2701
|
+
hasCriticalError = true;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2632
2704
|
}
|
|
2633
2705
|
console.log(`\u{1F9E0} Pensamiento: ${response.thought}`);
|
|
2634
2706
|
if (!response.finish && history.length >= 6) {
|
|
@@ -2771,6 +2843,9 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2771
2843
|
} else if (failureKeywords.test(fbText.toLowerCase())) {
|
|
2772
2844
|
console.log(`>>ARCALITY_STATUS>> \u{1F4DA} [BUSINESS RULE] Error de validaci\xF3n capturado: "${fbText.substring(0, 80)}"`);
|
|
2773
2845
|
captureValidationRule(fbText.trim(), `Acci\xF3n: click en "${desc}" en ${page.url()}`);
|
|
2846
|
+
const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(fbText);
|
|
2847
|
+
const correctionHint = isDuplicateError ? `CORRECCI\xD3N OBLIGATORIA: El valor que ingresaste ya existe. Ve al campo que lo caus\xF3 y c\xE1mbialo por uno \xFAnico (a\xF1ade _${Date.now().toString().slice(-4)} como sufijo).` : `CORRECCI\xD3N OBLIGATORIA: Corrige el campo se\xF1alado antes de intentar guardar de nuevo.`;
|
|
2848
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR POST-GUARDADO: "${fbText.trim().substring(0, 200)}". ${correctionHint}`);
|
|
2774
2849
|
}
|
|
2775
2850
|
}
|
|
2776
2851
|
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
const baseDir = 'c:/Users/apulido/OneDrive - Block Networks/Documentos/Arcadial Projects/Char.Dev/out/qmsDev';
|
|
5
|
-
const nestedDir = path.join(baseDir, 'context', 'qmsDev');
|
|
6
|
-
const targetContextDir = path.join(baseDir, 'context');
|
|
7
|
-
const targetMissionsDir = path.join(baseDir, 'missions');
|
|
8
|
-
|
|
9
|
-
function cleanup() {
|
|
10
|
-
if (!fs.existsSync(nestedDir)) {
|
|
11
|
-
console.log('Nested directory not found, skipping.');
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const files = fs.readdirSync(nestedDir);
|
|
16
|
-
|
|
17
|
-
files.forEach(file => {
|
|
18
|
-
const filePath = path.join(nestedDir, file);
|
|
19
|
-
if (file.startsWith('agent-log-')) {
|
|
20
|
-
console.log(`Moving ${file} to context/`);
|
|
21
|
-
fs.renameSync(filePath, path.join(targetContextDir, file));
|
|
22
|
-
} else if (file === 'memoria-agente.json') {
|
|
23
|
-
const targetMemory = path.join(targetContextDir, 'memoria-agente.json');
|
|
24
|
-
if (fs.existsSync(targetMemory)) {
|
|
25
|
-
console.log('Merging memoria-agente.json...');
|
|
26
|
-
const mainMemory = JSON.parse(fs.readFileSync(targetMemory, 'utf8'));
|
|
27
|
-
const nestedMemory = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
28
|
-
const combined = [...mainMemory, ...nestedMemory];
|
|
29
|
-
fs.writeFileSync(targetMemory, JSON.stringify(combined, null, 2));
|
|
30
|
-
fs.unlinkSync(filePath);
|
|
31
|
-
} else {
|
|
32
|
-
console.log('Moving memoria-agente.json...');
|
|
33
|
-
fs.renameSync(filePath, targetMemory);
|
|
34
|
-
}
|
|
35
|
-
} else if (file === 'saved_missions') {
|
|
36
|
-
const missions = fs.readdirSync(filePath);
|
|
37
|
-
missions.forEach(m => {
|
|
38
|
-
if (m.endsWith('.yaml') || m.endsWith('.yml')) {
|
|
39
|
-
console.log(`Moving mission ${m} to missions/`);
|
|
40
|
-
fs.renameSync(path.join(filePath, m), path.join(targetMissionsDir, m));
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
fs.rmdirSync(filePath, { recursive: true });
|
|
44
|
-
} else if (fs.statSync(filePath).isFile()) {
|
|
45
|
-
console.log(`Moving other file ${file} to context/`);
|
|
46
|
-
fs.renameSync(filePath, path.join(targetContextDir, file));
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Try to remove the empty nested dir
|
|
51
|
-
try {
|
|
52
|
-
if (fs.readdirSync(nestedDir).length === 0) {
|
|
53
|
-
fs.rmdirSync(nestedDir);
|
|
54
|
-
console.log('Removed empty nested directory.');
|
|
55
|
-
} else {
|
|
56
|
-
console.warn('Nested directory not empty, keeping it.');
|
|
57
|
-
}
|
|
58
|
-
} catch (e) {
|
|
59
|
-
console.error('Error removing nested dir:', e.message);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
cleanup();
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import 'dotenv/config';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
|
|
7
|
-
const targetPath = process.argv[2] || '/';
|
|
8
|
-
const isWin = process.platform === 'win32';
|
|
9
|
-
const cmd = isWin ? 'npx.cmd' : 'npx';
|
|
10
|
-
const testFile = 'tests/_helpers/discover-view.spec.ts';
|
|
11
|
-
|
|
12
|
-
function parseDotEnvFile(p) {
|
|
13
|
-
try {
|
|
14
|
-
const raw = fs.readFileSync(p, 'utf8');
|
|
15
|
-
const lines = raw.split(/\r?\n/);
|
|
16
|
-
const out = {};
|
|
17
|
-
for (const line of lines) {
|
|
18
|
-
const trimmed = line.trim();
|
|
19
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
20
|
-
const idx = trimmed.indexOf('=');
|
|
21
|
-
if (idx === -1) continue;
|
|
22
|
-
let k = trimmed.slice(0, idx).trim();
|
|
23
|
-
let v = trimmed.slice(idx + 1).trim();
|
|
24
|
-
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
|
25
|
-
out[k] = v;
|
|
26
|
-
}
|
|
27
|
-
return out;
|
|
28
|
-
} catch {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const fileEnv = parseDotEnvFile(path.join(process.cwd(), '.env'));
|
|
34
|
-
const env = { ...process.env, ...fileEnv, TARGET_PATH: targetPath };
|
|
35
|
-
|
|
36
|
-
const args = ['playwright', 'test', testFile, '--project=chromium'];
|
|
37
|
-
// Debug printing removed to avoid leaking placeholders; command is spawned directly
|
|
38
|
-
|
|
39
|
-
const child = spawn(cmd, args, {
|
|
40
|
-
stdio: 'inherit',
|
|
41
|
-
shell: isWin,
|
|
42
|
-
env,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
child.on('exit', (code) => {
|
|
46
|
-
process.exit(code ?? 0);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
child.on('error', (err) => {
|
|
50
|
-
console.error('Failed to run playwright:', err);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
});
|