@arcadialdev/arcality 2.2.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.
Files changed (97) hide show
  1. package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
  2. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  3. package/.agents/skills/frontend-design/SKILL.md +42 -0
  4. package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  5. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  6. package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
  7. package/.agents/skills/playwright-best-practices/README.md +147 -0
  8. package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
  9. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
  10. package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
  11. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
  12. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
  13. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
  14. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
  15. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
  16. package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
  17. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
  18. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
  19. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
  20. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
  21. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
  22. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
  23. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
  24. package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
  25. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
  26. package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
  27. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
  28. package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
  29. package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
  30. package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
  31. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
  32. package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
  33. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
  34. package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
  35. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
  36. package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
  37. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
  38. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
  39. package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
  40. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
  41. package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
  42. package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
  43. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
  44. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
  45. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
  46. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
  52. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
  53. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
  54. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
  55. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
  56. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
  57. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
  58. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
  59. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
  60. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
  61. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
  62. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
  63. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
  64. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
  65. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
  66. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
  67. package/.env.example +21 -0
  68. package/README.md +30 -0
  69. package/bin/arcality.mjs +86 -0
  70. package/package.json +66 -0
  71. package/playwright.config.ts +12 -0
  72. package/scripts/cleanup-qmsdev.mjs +63 -0
  73. package/scripts/discover-view.mjs +52 -0
  74. package/scripts/extract-view.mjs +64 -0
  75. package/scripts/gen-and-run.mjs +838 -0
  76. package/scripts/init.mjs +290 -0
  77. package/scripts/migrate-to-central-out.mjs +157 -0
  78. package/scripts/postinstall.mjs +63 -0
  79. package/scripts/rebrand-report.mjs +241 -0
  80. package/scripts/setup.mjs +166 -0
  81. package/src/KnowledgeService.ts +239 -0
  82. package/src/arcalityClient.mjs +266 -0
  83. package/src/configLoader.mjs +179 -0
  84. package/src/configManager.mjs +172 -0
  85. package/src/consoleBanner.ts +32 -0
  86. package/src/envSetup.ts +205 -0
  87. package/src/index.ts +25 -0
  88. package/src/projectInspector.ts +42 -0
  89. package/src/services/collectiveMemoryService.ts +178 -0
  90. package/src/testRunner.ts +201 -0
  91. package/tests/_helpers/ArcalityReporter.ts +490 -0
  92. package/tests/_helpers/agentic-runner.spec.ts +741 -0
  93. package/tests/_helpers/ai-agent-helper.ts +1573 -0
  94. package/tests/_helpers/discover-view.spec.ts +238 -0
  95. package/tests/_helpers/extract-view.spec.ts +118 -0
  96. package/tests/_helpers/qa-tools.ts +333 -0
  97. package/tests/_helpers/smart-action.spec.ts +1458 -0
@@ -0,0 +1,238 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import 'dotenv/config';
4
+ import { test, expect } from '@playwright/test';
5
+
6
+ test('discover visible components on view', async ({ page }) => {
7
+ const target = process.env.TARGET_PATH || '/';
8
+ const base = process.env.BASE_URL || '';
9
+
10
+ // Perform canonical login if credentials available
11
+ const user = process.env.LOGIN_USER || '';
12
+ const pass = process.env.LOGIN_PASSWORD || '';
13
+ if (user && pass) {
14
+ const loginUrl = base + '/login';
15
+ console.log('Logging in via', loginUrl);
16
+ await page.goto(loginUrl);
17
+
18
+ // Generic selector strategies
19
+ const userSelectors = [
20
+ 'input[type="email"]', 'input[name="email"]', 'input[name="username"]',
21
+ 'input[name="user"]', 'input[name="login"]', 'input[id="email"]',
22
+ 'input[id="username"]', 'input[placeholder*="user" i]',
23
+ 'input[placeholder*="usuario" i]', 'input[placeholder*="mail" i]'
24
+ ];
25
+ const passSelectors = [
26
+ 'input[type="password"]', 'input[name="password"]',
27
+ 'input[name="pass"]', 'input[id="password"]',
28
+ 'input[placeholder*="pass" i]', 'input[placeholder*="contra" i]'
29
+ ];
30
+ const submitSelectors = [
31
+ 'button[type="submit"]', 'input[type="submit"]',
32
+ 'button:has-text("Login")', 'button:has-text("Sign in")',
33
+ 'button:has-text("Entrar")', 'button:has-text("Ingresar")',
34
+ 'button:has-text("Iniciar")', 'button:has-text("Acceder")',
35
+ '[role="button"]:has-text("Login")', '[role="button"]:has-text("Entrar")'
36
+ ];
37
+
38
+ let email = null;
39
+ for (const s of userSelectors) {
40
+ const loc = page.locator(s).first();
41
+ if (await loc.isVisible().catch(() => false)) {
42
+ email = loc;
43
+ console.log('Found user field:', s);
44
+ break;
45
+ }
46
+ }
47
+
48
+ let password = null;
49
+ for (const s of passSelectors) {
50
+ const loc = page.locator(s).first();
51
+ if (await loc.isVisible().catch(() => false)) {
52
+ password = loc;
53
+ console.log('Found password field:', s);
54
+ break;
55
+ }
56
+ }
57
+
58
+ let submit = null;
59
+ for (const s of submitSelectors) {
60
+ const loc = page.locator(s).first();
61
+ if (await loc.isVisible().catch(() => false)) {
62
+ submit = loc;
63
+ console.log('Found submit button:', s);
64
+ break;
65
+ }
66
+ }
67
+
68
+ if (email && password && submit) {
69
+ await email.fill(user);
70
+ await password.fill(pass);
71
+ await submit.click();
72
+ await expect(page).not.toHaveURL(/\/login(\b|$)/i, { timeout: 15000 }).catch(e => console.warn('Login URL check timeout/fail, continuing...'));
73
+ try {
74
+ await page.locator('nav, aside, [role="navigation"], [data-testid="sidebar"]').first().waitFor({ state: 'visible', timeout: 5000 });
75
+ } catch { }
76
+ } else {
77
+ console.warn('Could not detect all login fields (user, password, submit) - skipping auto-login step.');
78
+ }
79
+ }
80
+
81
+ const url = base + target;
82
+ console.log('Navigating to', url);
83
+ await page.goto(url);
84
+ await page.waitForLoadState('networkidle');
85
+
86
+ // Optionally wait for main container if present
87
+ try {
88
+ await page.locator('main, [role="main"]').first().waitFor({ state: 'visible', timeout: 3000 });
89
+ } catch { }
90
+
91
+ // Gather visible interactive elements and some metadata
92
+ const components = await page.evaluate(() => {
93
+ function isVisible(el: any) {
94
+ if (!el) return false;
95
+ const style = window.getComputedStyle(el);
96
+ if (style && (style.visibility === 'hidden' || style.display === 'none' || +style.opacity === 0)) return false;
97
+ const rect = el.getBoundingClientRect();
98
+ return rect.width > 0 && rect.height > 0;
99
+ }
100
+
101
+ function cssPath(el: any) {
102
+ if (!(el instanceof Element)) return '';
103
+ const path = [];
104
+ while (el && el.nodeType === Node.ELEMENT_NODE && el.tagName.toLowerCase() !== 'html') {
105
+ let selector = el.tagName.toLowerCase();
106
+ if (el.id) selector += `#${el.id}`;
107
+ else {
108
+ const cls = Array.from(el.classList || []).slice(0, 3).map((c: any) => c.replace(/[^a-z0-9_-]/gi, '')).join('.');
109
+ if (cls) selector += `.${cls}`;
110
+ }
111
+ const siblingIndex = Array.from(el.parentNode ? el.parentNode.children : []).filter((x: any) => x.tagName === el.tagName).indexOf(el) + 1;
112
+ if (siblingIndex > 1) selector += `:nth-of-type(${siblingIndex})`;
113
+ path.unshift(selector);
114
+ el = el.parentElement;
115
+ }
116
+ return path.join(' > ');
117
+ }
118
+
119
+ function nearestHeading(el: any) {
120
+ let cur = el;
121
+ while (cur) {
122
+ const headings = Array.from(cur.querySelectorAll('h1,h2,h3,h4,h5,h6')) as any[];
123
+ const h = headings.find((x) => x && x.textContent && x.textContent.trim());
124
+ if (h) return (h.textContent as string).trim().slice(0, 200);
125
+ cur = cur.parentElement;
126
+ }
127
+ return '';
128
+ }
129
+
130
+ function nearestLabelForInput(el: any) {
131
+ if (!el) return '';
132
+ if (el.id) {
133
+ const lbl = document.querySelector(`label[for="${el.id}"]`) as any;
134
+ if (lbl && lbl.textContent) return (lbl.textContent as string).trim().slice(0, 200);
135
+ }
136
+ // find closest preceding label sibling
137
+ let cur = el.previousElementSibling as any;
138
+ while (cur) {
139
+ if (cur.tagName?.toLowerCase() === 'label' && cur.textContent) return (cur.textContent as string).trim().slice(0, 200);
140
+ cur = cur.previousElementSibling;
141
+ }
142
+ // try parent label
143
+ const parentLabel = el.closest('label') as any;
144
+ if (parentLabel && parentLabel.textContent) return (parentLabel.textContent as string).trim().slice(0, 200);
145
+ return '';
146
+ }
147
+
148
+ function attributesMap(el: any) {
149
+ const out: Record<string, string> = {};
150
+ if (!(el instanceof Element)) return out;
151
+ for (const a of Array.from(el.attributes || [])) {
152
+ out[a.name] = a.value;
153
+ }
154
+ return out;
155
+ }
156
+
157
+ const nodes = Array.from(document.querySelectorAll('a, button, input, textarea, select, [role], label, h1,h2,h3,h4,h5,h6'))
158
+ .filter(isVisible);
159
+
160
+ const list = nodes.map((el: any) => {
161
+ const tag = el.tagName.toLowerCase();
162
+ const attrs = attributesMap(el);
163
+ const text = (el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 400);
164
+ const aria = attrs['aria-label'] || attrs['aria-labelledby'] || '';
165
+ const placeholder = attrs['placeholder'] || '';
166
+ const id = el.id || '';
167
+ const name = attrs['name'] || '';
168
+ const rect = el.getBoundingClientRect();
169
+ const parentText = (el.parentElement && el.parentElement.textContent)
170
+ ? (el.parentElement.textContent as string).trim().replace(/\s+/g, ' ').slice(0, 200)
171
+ : '';
172
+
173
+ return {
174
+ tag,
175
+ id,
176
+ name,
177
+ type: attrs['type'] || '',
178
+ text,
179
+ aria,
180
+ placeholder,
181
+ attributes: attrs,
182
+ data: Object.keys(attrs).filter(k => k.startsWith('data-')).reduce((acc, k) => { acc[k] = attrs[k]; return acc; }, {} as Record<string, string>),
183
+ boundingRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
184
+ nearestHeading: nearestHeading(el),
185
+ parentText,
186
+ label: (tag === 'input' || tag === 'textarea' || tag === 'select') ? nearestLabelForInput(el) : '',
187
+ selector: cssPath(el),
188
+ };
189
+ });
190
+
191
+ return { timestamp: Date.now(), url: location.href, components: list };
192
+ });
193
+
194
+ const outDir = process.env.DOMAIN_DIR || path.join(process.cwd(), 'out');
195
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
196
+ const filePath = path.join(outDir, `components-${Date.now()}.json`);
197
+ fs.writeFileSync(filePath, JSON.stringify({ url, timestamp: new Date().toISOString(), components }, null, 2), 'utf8');
198
+
199
+ console.log('Wrote components list to', filePath, 'found', components.components.length, 'items');
200
+
201
+ // Optional: autofill fields based on discovered components
202
+ if (process.env.AUTOFILL === 'true') {
203
+ console.log('AUTOFILL enabled — filling detected fields (skipping login fields)');
204
+ for (const c of (components as any).components) {
205
+ try {
206
+ // Skip login fields
207
+ if (c.selector && /input\[type="email"\]|input\[type="password"\]/i.test(c.selector)) continue;
208
+
209
+ const locator = page.locator(c.selector);
210
+ if ((await locator.count()) === 0) continue;
211
+
212
+ if (c.tag === 'input') {
213
+ const t = (c.type || '').toLowerCase();
214
+ if (t === 'email') await locator.fill(`test+${Date.now()}@example.com`).catch(() => { });
215
+ else if (t === 'password') await locator.fill(`P@ssw0rd!${Math.floor(Math.random() * 90000)}`).catch(() => { });
216
+ else if (t === 'tel') await locator.fill('5551234567').catch(() => { });
217
+ else if (t === 'number') await locator.fill('1').catch(() => { });
218
+ else await locator.fill('sample text').catch(() => { });
219
+ } else if (c.tag === 'textarea') {
220
+ await locator.fill('sample text').catch(() => { });
221
+ } else if (c.tag === 'select') {
222
+ // try to select a non-empty option
223
+ await locator.selectOption({ index: 1 }).catch(() => { });
224
+ }
225
+ } catch (e) {
226
+ // ignore individual fill errors
227
+ }
228
+ }
229
+
230
+ if (process.env.AUTOSUBMIT === 'true') {
231
+ console.log('AUTOSUBMIT enabled — attempting to submit form');
232
+ try {
233
+ const submit = page.locator('button[type="submit"], input[type="submit"]').first();
234
+ if (await submit.count()) await submit.click().catch(() => { });
235
+ } catch { }
236
+ }
237
+ }
238
+ });
@@ -0,0 +1,118 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import 'dotenv/config';
4
+ import { test, expect } from '@playwright/test';
5
+
6
+ test('extract view DOM and accessibility snapshot', async ({ page }) => {
7
+ const target = process.env.TARGET_PATH || '/';
8
+ const base = process.env.BASE_URL || '';
9
+
10
+ // Perform canonical login if credentials available
11
+ const user = process.env.LOGIN_USER || '';
12
+ const pass = process.env.LOGIN_PASSWORD || '';
13
+ if (user && pass) {
14
+ const loginUrl = base + '/login';
15
+ console.log('Logging in via', loginUrl);
16
+ await page.goto(loginUrl);
17
+
18
+ // Generic selector strategies
19
+ const userSelectors = [
20
+ 'input[type="email"]', 'input[name="email"]', 'input[name="username"]',
21
+ 'input[name="user"]', 'input[name="login"]', 'input[id="email"]',
22
+ 'input[id="username"]', 'input[placeholder*="user" i]',
23
+ 'input[placeholder*="usuario" i]', 'input[placeholder*="mail" i]'
24
+ ];
25
+ const passSelectors = [
26
+ 'input[type="password"]', 'input[name="password"]',
27
+ 'input[name="pass"]', 'input[id="password"]',
28
+ 'input[placeholder*="pass" i]', 'input[placeholder*="contra" i]'
29
+ ];
30
+ const submitSelectors = [
31
+ 'button[type="submit"]', 'input[type="submit"]',
32
+ 'button:has-text("Login")', 'button:has-text("Sign in")',
33
+ 'button:has-text("Entrar")', 'button:has-text("Ingresar")',
34
+ 'button:has-text("Iniciar")', 'button:has-text("Acceder")',
35
+ '[role="button"]:has-text("Login")', '[role="button"]:has-text("Entrar")'
36
+ ];
37
+
38
+ let email = null;
39
+ for (const s of userSelectors) {
40
+ const loc = page.locator(s).first();
41
+ if (await loc.isVisible().catch(() => false)) {
42
+ email = loc;
43
+ console.log('Found user field:', s);
44
+ break;
45
+ }
46
+ }
47
+
48
+ let password = null;
49
+ for (const s of passSelectors) {
50
+ const loc = page.locator(s).first();
51
+ if (await loc.isVisible().catch(() => false)) {
52
+ password = loc;
53
+ console.log('Found password field:', s);
54
+ break;
55
+ }
56
+ }
57
+
58
+ let submit = null;
59
+ for (const s of submitSelectors) {
60
+ const loc = page.locator(s).first();
61
+ if (await loc.isVisible().catch(() => false)) {
62
+ submit = loc;
63
+ console.log('Found submit button:', s);
64
+ break;
65
+ }
66
+ }
67
+
68
+ if (email && password && submit) {
69
+ await email.fill(user);
70
+ await password.fill(pass);
71
+ await submit.click();
72
+ await expect(page).not.toHaveURL(/\/login(\b|$)/i, { timeout: 15000 }).catch(e => console.warn('Login URL check timeout/fail, continuing...'));
73
+ try {
74
+ await page.locator('nav, aside, [role="navigation"], [data-testid="sidebar"]').first().waitFor({ state: 'visible', timeout: 5000 });
75
+ } catch { }
76
+ } else {
77
+ console.warn('Could not detect all login fields (user, password, submit) - skipping auto-login step.');
78
+ }
79
+ }
80
+
81
+ const url = base + target;
82
+ console.log('Navigating to', url);
83
+ await page.goto(url);
84
+ await page.waitForLoadState('networkidle');
85
+
86
+ // Capture full HTML
87
+ const html = await page.content();
88
+
89
+ // Capture accessibility tree (helps detect components/roles/labels)
90
+ // Note: page.accessibility was deprecated/removed in Playwright 1.38+
91
+ let accessibility = null;
92
+ try {
93
+ accessibility = await (page as any).accessibility?.snapshot({ interestingOnly: false });
94
+ } catch (e: any) {
95
+ console.warn('Accessibility snapshot failed:', e?.message ?? e);
96
+ }
97
+
98
+ const outDir = process.env.DOMAIN_DIR || path.join(process.cwd(), 'out');
99
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
100
+
101
+ const filePath = path.join(outDir, `view-snapshot-${Date.now()}.json`);
102
+ fs.writeFileSync(
103
+ filePath,
104
+ JSON.stringify(
105
+ {
106
+ url,
107
+ timestamp: new Date().toISOString(),
108
+ html,
109
+ accessibility,
110
+ },
111
+ null,
112
+ 2
113
+ ),
114
+ 'utf8'
115
+ );
116
+
117
+ console.log('Wrote view snapshot to', filePath);
118
+ });