design-clone 2.1.0 → 2.3.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 (177) hide show
  1. package/README.md +13 -34
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -171
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +9 -86
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +7 -14
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +2 -22
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +533 -286
  15. package/docs/index.md +74 -0
  16. package/docs/project-overview-pdr.md +797 -0
  17. package/docs/system-architecture.md +718 -0
  18. package/package.json +14 -17
  19. package/src/ai/prompts/design-tokens/basic.md +80 -0
  20. package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
  21. package/src/ai/prompts/design-tokens/section.md +48 -0
  22. package/src/ai/prompts/design-tokens/with-css.md +87 -0
  23. package/src/ai/prompts/structure-analysis/basic.md +55 -0
  24. package/src/ai/prompts/structure-analysis/with-context.md +59 -0
  25. package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
  26. package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
  27. package/src/ai/prompts/ux-audit/aggregation.md +42 -0
  28. package/src/ai/prompts/ux-audit/desktop.md +92 -0
  29. package/src/ai/prompts/ux-audit/mobile.md +93 -0
  30. package/src/ai/prompts/ux-audit/tablet.md +92 -0
  31. package/src/core/animation/animation-extractor-ast.js +183 -0
  32. package/src/core/animation/animation-extractor-output.js +152 -0
  33. package/src/core/animation/animation-extractor.js +178 -0
  34. package/src/core/animation/state-capture-detection.js +200 -0
  35. package/src/core/animation/state-capture.js +193 -0
  36. package/src/core/capture/browser-context-pool.js +96 -0
  37. package/src/core/capture/multi-page-screenshot-page.js +110 -0
  38. package/src/core/capture/multi-page-screenshot.js +208 -0
  39. package/src/core/capture/screenshot-extraction.js +186 -0
  40. package/src/core/capture/screenshot-helpers.js +175 -0
  41. package/src/core/capture/screenshot-orchestrator.js +174 -0
  42. package/src/core/capture/screenshot-viewport.js +93 -0
  43. package/src/core/capture/screenshot.js +192 -0
  44. package/src/core/content/content-counter-dom.js +191 -0
  45. package/src/core/content/content-counter.js +76 -0
  46. package/src/core/css/breakpoint-detector.js +66 -0
  47. package/src/core/css/chromium-defaults.json +23 -0
  48. package/src/core/css/computed-style-extractor.js +102 -0
  49. package/src/core/css/css-chunker.js +103 -0
  50. package/src/core/css/filter-css-dead-code.js +120 -0
  51. package/src/core/css/filter-css-html-analyzer.js +110 -0
  52. package/src/core/css/filter-css-selector-matcher.js +172 -0
  53. package/src/core/css/filter-css.js +206 -0
  54. package/src/core/css/merge-css-atrule-processor.js +158 -0
  55. package/src/core/css/merge-css-file-io.js +68 -0
  56. package/src/core/css/merge-css.js +148 -0
  57. package/src/core/detection/framework-detector-routing.js +68 -0
  58. package/src/core/detection/framework-detector-signals.js +65 -0
  59. package/src/core/detection/framework-detector.js +198 -0
  60. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  61. package/src/core/dimension/dimension-extractor.js +317 -0
  62. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  63. package/src/core/dimension/dimension-output.js +173 -0
  64. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  65. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  66. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  67. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  68. package/src/core/discovery/app-state-snapshot.js +131 -0
  69. package/src/core/discovery/discover-pages-routes.js +84 -0
  70. package/src/core/discovery/discover-pages-utils.js +177 -0
  71. package/src/core/discovery/discover-pages.js +191 -0
  72. package/src/core/html/html-extractor-inline-styler.js +70 -0
  73. package/src/core/html/html-extractor.js +147 -0
  74. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  75. package/src/core/html/semantic-enhancer-page.js +148 -0
  76. package/src/core/html/semantic-enhancer.js +135 -0
  77. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  78. package/src/core/links/rewrite-links.js +173 -0
  79. package/src/core/media/asset-validator.js +118 -0
  80. package/src/core/media/extract-assets-downloader.js +187 -0
  81. package/src/core/media/extract-assets-page-scraper.js +115 -0
  82. package/src/core/media/extract-assets.js +159 -0
  83. package/src/core/media/video-capture-convert.js +200 -0
  84. package/src/core/media/video-capture.js +201 -0
  85. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
  86. package/src/core/section/section-cropper-helpers.js +43 -0
  87. package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
  88. package/src/core/section/section-detector-strategies.js +139 -0
  89. package/src/core/section/section-detector-utils.js +100 -0
  90. package/src/core/section/section-detector.js +88 -0
  91. package/src/core/tests/test-section-cropper.js +2 -2
  92. package/src/core/tests/test-section-detector.js +2 -2
  93. package/src/post-process/enhance-assets.js +29 -4
  94. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  95. package/src/post-process/fetch-images.js +60 -263
  96. package/src/post-process/inject-gosnap.js +88 -0
  97. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  98. package/src/post-process/inject-icons.js +47 -200
  99. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  100. package/src/route-discoverers/base-discoverer.js +29 -118
  101. package/src/route-discoverers/index.js +1 -1
  102. package/src/shared/config.js +38 -0
  103. package/src/shared/error-codes.js +31 -0
  104. package/src/shared/viewports.js +46 -0
  105. package/src/utils/browser.js +0 -7
  106. package/src/utils/helpers.js +4 -0
  107. package/src/utils/log.js +12 -0
  108. package/src/utils/playwright-loader.js +76 -0
  109. package/src/utils/playwright.js +3 -69
  110. package/src/utils/progress.js +32 -0
  111. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  112. package/src/verification/generate-audit-report-sections.js +158 -0
  113. package/src/verification/generate-audit-report.js +5 -281
  114. package/src/verification/quality-scorer.js +92 -0
  115. package/src/verification/verify-footer-checks.js +103 -0
  116. package/src/verification/verify-footer-helpers.js +178 -0
  117. package/src/verification/verify-footer.js +23 -381
  118. package/src/verification/verify-header-checks.js +104 -0
  119. package/src/verification/verify-header-helpers.js +156 -0
  120. package/src/verification/verify-header.js +23 -365
  121. package/src/verification/verify-layout-report.js +101 -0
  122. package/src/verification/verify-layout.js +13 -259
  123. package/src/verification/verify-menu-checks.js +104 -0
  124. package/src/verification/verify-menu-helpers.js +112 -0
  125. package/src/verification/verify-menu.js +17 -285
  126. package/src/verification/verify-slider-checks.js +115 -0
  127. package/src/verification/verify-slider-constants.js +65 -0
  128. package/src/verification/verify-slider-helpers.js +164 -0
  129. package/src/verification/verify-slider.js +23 -414
  130. package/.env.example +0 -14
  131. package/docs/basic-clone.md +0 -63
  132. package/docs/cli-reference.md +0 -316
  133. package/docs/design-clone-architecture.md +0 -492
  134. package/docs/pixel-perfect.md +0 -117
  135. package/docs/project-roadmap.md +0 -382
  136. package/docs/troubleshooting.md +0 -170
  137. package/requirements.txt +0 -5
  138. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  139. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  140. package/src/ai/analyze-structure.py +0 -375
  141. package/src/ai/extract-design-tokens.py +0 -782
  142. package/src/ai/prompts/__init__.py +0 -2
  143. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  144. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  145. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  146. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  147. package/src/ai/prompts/design_tokens.py +0 -316
  148. package/src/ai/prompts/structure_analysis.py +0 -592
  149. package/src/ai/prompts/ux_audit.py +0 -198
  150. package/src/ai/ux-audit.js +0 -596
  151. package/src/core/animation-extractor.js +0 -526
  152. package/src/core/app-state-snapshot.js +0 -511
  153. package/src/core/content-counter.js +0 -342
  154. package/src/core/design-tokens.js +0 -103
  155. package/src/core/dimension-extractor.js +0 -438
  156. package/src/core/dimension-output.js +0 -305
  157. package/src/core/discover-pages.js +0 -542
  158. package/src/core/dom-tree-analyzer.js +0 -298
  159. package/src/core/extract-assets.js +0 -468
  160. package/src/core/filter-css.js +0 -499
  161. package/src/core/framework-detector.js +0 -538
  162. package/src/core/html-extractor.js +0 -212
  163. package/src/core/merge-css.js +0 -407
  164. package/src/core/multi-page-screenshot.js +0 -380
  165. package/src/core/rewrite-links.js +0 -226
  166. package/src/core/screenshot.js +0 -701
  167. package/src/core/section-detector.js +0 -386
  168. package/src/core/semantic-enhancer.js +0 -492
  169. package/src/core/state-capture.js +0 -598
  170. package/src/core/video-capture.js +0 -546
  171. package/src/utils/__init__.py +0 -16
  172. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  173. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  174. package/src/utils/env.py +0 -134
  175. /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
  176. /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
  177. /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
@@ -18,124 +18,13 @@
18
18
  * --verbose Show detailed progress
19
19
  */
20
20
 
21
- import fs from 'fs/promises';
22
21
  import path from 'path';
23
- import { fileURLToPath } from 'url';
24
22
 
25
- // Import browser abstraction (auto-detects chrome-devtools or standalone)
26
- import { getBrowser, getPage, closeBrowser, disconnectBrowser, parseArgs, outputJSON, outputError } from '../utils/browser.js';
27
-
28
- // Viewport configurations
29
- const VIEWPORTS = {
30
- mobile: { width: 375, height: 812, deviceScaleFactor: 2 },
31
- tablet: { width: 768, height: 1024, deviceScaleFactor: 1 },
32
- desktop: { width: 1920, height: 1080, deviceScaleFactor: 1 }
33
- };
34
-
35
- // Common menu element selectors
36
- const MENU_SELECTORS = {
37
- // Hamburger/toggle buttons
38
- toggleButtons: [
39
- '[aria-label*="menu" i]',
40
- '[aria-label*="nav" i]',
41
- 'button.hamburger',
42
- '.hamburger',
43
- '.menu-toggle',
44
- '.nav-toggle',
45
- '.mobile-menu-toggle',
46
- 'button[class*="hamburger"]',
47
- 'button[class*="menu"]',
48
- '[data-toggle="nav"]',
49
- '[data-menu-toggle]',
50
- '.header__toggle',
51
- '.header-toggle',
52
- '#menu-toggle',
53
- '.burger',
54
- '.burger-menu'
55
- ],
56
- // Navigation containers
57
- navContainers: [
58
- 'nav',
59
- '[role="navigation"]',
60
- '.nav',
61
- '.navigation',
62
- '.main-nav',
63
- '.site-nav',
64
- '.header-nav',
65
- '.primary-nav',
66
- '#nav',
67
- '#navigation',
68
- '.menu',
69
- '.main-menu'
70
- ],
71
- // Menu items
72
- menuItems: [
73
- 'nav a',
74
- 'nav li',
75
- '.nav-item',
76
- '.menu-item',
77
- '.nav-link',
78
- '.menu-link',
79
- '[role="navigation"] a'
80
- ]
81
- };
82
-
83
- /**
84
- * Check if element is visible using Playwright locator API
85
- */
86
- async function isElementVisible(page, selector) {
87
- try {
88
- return await page.locator(selector).isVisible();
89
- } catch {
90
- return false;
91
- }
92
- }
93
-
94
- /**
95
- * Find first matching selector
96
- */
97
- async function findElement(page, selectors) {
98
- for (const selector of selectors) {
99
- const element = await page.$(selector);
100
- if (element) {
101
- return { element, selector };
102
- }
103
- }
104
- return null;
105
- }
106
-
107
- /**
108
- * Count visible menu items
109
- */
110
- async function countVisibleMenuItems(page) {
111
- for (const selector of MENU_SELECTORS.menuItems) {
112
- try {
113
- const count = await page.evaluate((sel) => {
114
- const items = document.querySelectorAll(sel);
115
- let visible = 0;
116
- items.forEach(item => {
117
- const style = window.getComputedStyle(item);
118
- const rect = item.getBoundingClientRect();
119
- if (
120
- style.display !== 'none' &&
121
- style.visibility !== 'hidden' &&
122
- style.opacity !== '0' &&
123
- rect.width > 0 &&
124
- rect.height > 0
125
- ) {
126
- visible++;
127
- }
128
- });
129
- return visible;
130
- }, selector);
131
-
132
- if (count > 0) {
133
- return { count, selector };
134
- }
135
- } catch { /* continue */ }
136
- }
137
- return { count: 0, selector: null };
138
- }
23
+ import { getBrowser, getPage, closeBrowser, disconnectBrowser } from '../utils/browser.js';
24
+ import { parseArgs, outputJSON, outputError } from '../utils/helpers.js';
25
+ import { VIEWPORTS_HD as VIEWPORTS } from '../shared/viewports.js';
26
+ import { MENU_SELECTORS, findElement, countVisibleMenuItems } from './verify-menu-helpers.js';
27
+ import { testDesktopMenu, testMobileMenu } from './verify-menu-checks.js';
139
28
 
140
29
  /**
141
30
  * Test menu at specific viewport
@@ -143,151 +32,28 @@ async function countVisibleMenuItems(page) {
143
32
  async function testViewport(page, viewportName, verbose = false) {
144
33
  const viewport = VIEWPORTS[viewportName];
145
34
  await page.setViewportSize(viewport);
146
- await new Promise(r => setTimeout(r, 500)); // Wait for CSS to apply
147
-
148
- const result = {
149
- viewport: viewportName,
150
- dimensions: viewport,
151
- tests: [],
152
- passed: 0,
153
- failed: 0,
154
- warnings: []
155
- };
35
+ await new Promise(r => setTimeout(r, 500));
156
36
 
37
+ const result = { viewport: viewportName, dimensions: viewport, tests: [], passed: 0, failed: 0, warnings: [] };
157
38
  if (verbose) console.error(`\n📱 Testing ${viewportName} (${viewport.width}x${viewport.height})...`);
158
39
 
159
- // Test 1: Navigation container exists
160
40
  const navResult = await findElement(page, MENU_SELECTORS.navContainers);
161
41
  if (navResult) {
162
- result.tests.push({
163
- name: 'Navigation container exists',
164
- passed: true,
165
- selector: navResult.selector
166
- });
42
+ result.tests.push({ name: 'Navigation container exists', passed: true, selector: navResult.selector });
167
43
  result.passed++;
168
44
  if (verbose) console.error(` ✓ Navigation container found: ${navResult.selector}`);
169
45
  } else {
170
- result.tests.push({
171
- name: 'Navigation container exists',
172
- passed: false,
173
- error: 'No navigation container found'
174
- });
46
+ result.tests.push({ name: 'Navigation container exists', passed: false, error: 'No navigation container found' });
175
47
  result.failed++;
176
48
  if (verbose) console.error(` ✗ Navigation container not found`);
177
49
  }
178
50
 
179
- // Test 2: Menu items visibility
180
51
  const menuItems = await countVisibleMenuItems(page);
181
52
 
182
- // Different expectations based on viewport
183
53
  if (viewportName === 'desktop') {
184
- // Desktop should have visible menu items
185
- if (menuItems.count >= 2) {
186
- result.tests.push({
187
- name: 'Desktop menu items visible',
188
- passed: true,
189
- count: menuItems.count,
190
- selector: menuItems.selector
191
- });
192
- result.passed++;
193
- if (verbose) console.error(` ✓ ${menuItems.count} menu items visible`);
194
- } else {
195
- result.tests.push({
196
- name: 'Desktop menu items visible',
197
- passed: false,
198
- count: menuItems.count,
199
- error: 'Expected at least 2 visible menu items on desktop'
200
- });
201
- result.failed++;
202
- if (verbose) console.error(` ✗ Only ${menuItems.count} menu items visible (expected >= 2)`);
203
- }
54
+ await testDesktopMenu(page, result, menuItems, verbose);
204
55
  } else {
205
- // Mobile/Tablet - check for hamburger menu
206
- const toggleResult = await findElement(page, MENU_SELECTORS.toggleButtons);
207
-
208
- if (toggleResult) {
209
- const isToggleVisible = await isElementVisible(page, toggleResult.selector);
210
-
211
- if (isToggleVisible) {
212
- result.tests.push({
213
- name: 'Mobile menu toggle visible',
214
- passed: true,
215
- selector: toggleResult.selector
216
- });
217
- result.passed++;
218
- if (verbose) console.error(` ✓ Menu toggle visible: ${toggleResult.selector}`);
219
-
220
- // Test toggle functionality
221
- try {
222
- // Get initial menu state
223
- const initialMenuItems = await countVisibleMenuItems(page);
224
-
225
- // Click toggle
226
- await toggleResult.element.click();
227
- await new Promise(r => setTimeout(r, 500)); // Wait for animation
228
-
229
- // Check menu state after click
230
- const afterClickItems = await countVisibleMenuItems(page);
231
-
232
- // Menu should either show more items or we can detect state change
233
- if (afterClickItems.count !== initialMenuItems.count || afterClickItems.count >= 2) {
234
- result.tests.push({
235
- name: 'Menu toggle functionality',
236
- passed: true,
237
- before: initialMenuItems.count,
238
- after: afterClickItems.count
239
- });
240
- result.passed++;
241
- if (verbose) console.error(` ✓ Toggle works: ${initialMenuItems.count} -> ${afterClickItems.count} items`);
242
-
243
- // Click again to close
244
- await toggleResult.element.click();
245
- await new Promise(r => setTimeout(r, 300));
246
- } else {
247
- result.tests.push({
248
- name: 'Menu toggle functionality',
249
- passed: false,
250
- before: initialMenuItems.count,
251
- after: afterClickItems.count,
252
- warning: 'Toggle may not be functional - no state change detected'
253
- });
254
- result.warnings.push('Menu toggle click did not change visible items');
255
- if (verbose) console.error(` ⚠ Toggle click had no effect`);
256
- }
257
- } catch (err) {
258
- result.tests.push({
259
- name: 'Menu toggle functionality',
260
- passed: false,
261
- error: err.message
262
- });
263
- result.failed++;
264
- if (verbose) console.error(` ✗ Toggle click failed: ${err.message}`);
265
- }
266
- } else {
267
- result.warnings.push('Menu toggle found but not visible');
268
- if (verbose) console.error(` ⚠ Menu toggle found but not visible`);
269
- }
270
- } else {
271
- // No hamburger - check if menu items are still visible (maybe it's a small visible menu)
272
- if (menuItems.count >= 2) {
273
- result.tests.push({
274
- name: 'Mobile menu visible without toggle',
275
- passed: true,
276
- count: menuItems.count,
277
- note: 'Menu shows items without hamburger toggle'
278
- });
279
- result.passed++;
280
- if (verbose) console.error(` ✓ ${menuItems.count} menu items visible (no toggle needed)`);
281
- } else {
282
- result.tests.push({
283
- name: 'Mobile menu accessibility',
284
- passed: false,
285
- error: 'No hamburger toggle found and menu items hidden'
286
- });
287
- result.failed++;
288
- if (verbose) console.error(` ✗ No hamburger toggle and menu items hidden`);
289
- }
290
- }
56
+ await testMobileMenu(page, result, menuItems, verbose);
291
57
  }
292
58
 
293
59
  return result;
@@ -298,12 +64,8 @@ async function testViewport(page, viewportName, verbose = false) {
298
64
  */
299
65
  async function captureDebugScreenshot(page, outputDir, viewportName) {
300
66
  if (!outputDir) return null;
301
-
302
67
  const screenshotPath = path.join(outputDir, `menu-test-${viewportName}.png`);
303
- await page.screenshot({
304
- path: screenshotPath,
305
- fullPage: false
306
- });
68
+ await page.screenshot({ path: screenshotPath, fullPage: false });
307
69
  return screenshotPath;
308
70
  }
309
71
 
@@ -322,38 +84,19 @@ async function verifyMenu() {
322
84
  const outputDir = args.output;
323
85
 
324
86
  try {
325
- // Launch browser
326
87
  const browser = await getBrowser({ headless: args.headless !== 'false' });
327
88
  const page = await getPage(browser);
328
89
 
329
- // Navigate to page
330
- let targetUrl;
331
- if (args.html) {
332
- // Local file
333
- const absolutePath = path.resolve(args.html);
334
- targetUrl = `file://${absolutePath}`;
335
- } else {
336
- targetUrl = args.url;
337
- }
338
-
90
+ const targetUrl = args.html ? `file://${path.resolve(args.html)}` : args.url;
339
91
  if (verbose) console.error(`\n🔍 Verifying responsive menu: ${targetUrl}\n`);
340
92
 
341
- await page.goto(targetUrl, {
342
- waitUntil: 'networkidle',
343
- timeout: 30000
344
- });
93
+ await page.goto(targetUrl, { waitUntil: 'networkidle', timeout: 30000 });
345
94
 
346
- // Test all viewports
347
95
  const results = {
348
96
  success: true,
349
97
  url: targetUrl,
350
98
  viewports: {},
351
- summary: {
352
- totalTests: 0,
353
- passed: 0,
354
- failed: 0,
355
- warnings: []
356
- },
99
+ summary: { totalTests: 0, passed: 0, failed: 0, warnings: [] },
357
100
  screenshots: []
358
101
  };
359
102
 
@@ -366,30 +109,20 @@ async function verifyMenu() {
366
109
  results.summary.failed += viewportResult.failed;
367
110
  results.summary.warnings.push(...viewportResult.warnings);
368
111
 
369
- // Capture debug screenshot if output dir provided
370
112
  if (outputDir) {
371
113
  const screenshotPath = await captureDebugScreenshot(page, outputDir, viewportName);
372
114
  if (screenshotPath) results.screenshots.push(screenshotPath);
373
115
  }
374
116
  }
375
117
 
376
- // Determine overall success
377
118
  results.success = results.summary.failed === 0;
378
119
 
379
- // Close browser
380
- if (args.close === 'true') {
381
- await closeBrowser();
382
- } else {
383
- await disconnectBrowser();
384
- }
120
+ if (args.close === 'true') { await closeBrowser(); } else { await disconnectBrowser(); }
385
121
 
386
- // Final summary
387
122
  if (verbose) {
388
123
  console.error('\n📊 Summary:');
389
124
  console.error(` Tests: ${results.summary.passed}/${results.summary.totalTests} passed`);
390
- if (results.summary.warnings.length > 0) {
391
- console.error(` Warnings: ${results.summary.warnings.length}`);
392
- }
125
+ if (results.summary.warnings.length > 0) console.error(` Warnings: ${results.summary.warnings.length}`);
393
126
  console.error(` Status: ${results.success ? '✓ PASS' : '✗ FAIL'}\n`);
394
127
  }
395
128
 
@@ -402,5 +135,4 @@ async function verifyMenu() {
402
135
  }
403
136
  }
404
137
 
405
- // Run
406
138
  verifyMenu();
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Slider Viewport Checks
3
+ *
4
+ * Orchestrates all per-viewport slider checks using helpers.
5
+ * Separated from verify-slider-helpers.js to keep each file under 200 lines.
6
+ */
7
+
8
+ import {
9
+ detectSliderLibrary,
10
+ isElementVisible,
11
+ countVisibleElements,
12
+ getActiveSlideIndex,
13
+ checkAutoplay
14
+ } from './verify-slider-helpers.js';
15
+
16
+ /**
17
+ * Test slider at a specific viewport — runs all slider checks
18
+ * @param {import('playwright').Page} page
19
+ * @param {string} viewportName
20
+ * @param {Object} VIEWPORTS - Viewport map
21
+ * @param {boolean} verbose
22
+ * @returns {Promise<Object>} Viewport result object
23
+ */
24
+ export async function testSliderViewport(page, viewportName, VIEWPORTS, verbose = false) {
25
+ const viewport = VIEWPORTS[viewportName];
26
+ await page.setViewportSize(viewport);
27
+ await new Promise(r => setTimeout(r, 500));
28
+
29
+ const result = {
30
+ viewport: viewportName,
31
+ dimensions: viewport,
32
+ tests: [],
33
+ passed: 0,
34
+ failed: 0,
35
+ warnings: [],
36
+ sliderInfo: null
37
+ };
38
+
39
+ if (verbose) console.error(`\n📱 Testing ${viewportName} (${viewport.width}x${viewport.height})...`);
40
+
41
+ const sliderDetection = await detectSliderLibrary(page);
42
+ if (!sliderDetection) {
43
+ result.tests.push({ name: 'Slider detection', passed: true, note: 'No slider/carousel detected on page' });
44
+ result.passed++;
45
+ if (verbose) console.error(` ℹ No slider detected`);
46
+ return result;
47
+ }
48
+
49
+ const { library, patterns } = sliderDetection;
50
+ result.sliderInfo = { library };
51
+ result.tests.push({ name: 'Slider detection', passed: true, library, selector: patterns.container });
52
+ result.passed++;
53
+ if (verbose) console.error(` ✓ Slider detected: ${library}`);
54
+
55
+ const slideCount = await countVisibleElements(page, patterns.slide);
56
+ if (slideCount > 0) {
57
+ result.tests.push({ name: 'Slides present', passed: true, count: slideCount });
58
+ result.passed++;
59
+ result.sliderInfo.slideCount = slideCount;
60
+ if (verbose) console.error(` ✓ ${slideCount} slides found`);
61
+ } else {
62
+ result.tests.push({ name: 'Slides present', passed: false, error: 'No slides found in slider' });
63
+ result.failed++;
64
+ if (verbose) console.error(` ✗ No slides found`);
65
+ }
66
+
67
+ const hasPrev = await isElementVisible(page, patterns.prev);
68
+ const hasNext = await isElementVisible(page, patterns.next);
69
+ if (hasPrev || hasNext) {
70
+ result.tests.push({ name: 'Navigation arrows', passed: true, hasPrev, hasNext });
71
+ result.passed++;
72
+ if (verbose) console.error(` ✓ Navigation arrows: prev=${hasPrev}, next=${hasNext}`);
73
+ } else {
74
+ result.warnings.push('No navigation arrows visible');
75
+ if (verbose) console.error(` ⚠ No navigation arrows found`);
76
+ }
77
+
78
+ const hasPagination = await isElementVisible(page, patterns.pagination);
79
+ if (hasPagination) {
80
+ result.tests.push({ name: 'Pagination dots', passed: true, selector: patterns.pagination });
81
+ result.passed++;
82
+ if (verbose) console.error(` ✓ Pagination dots found`);
83
+ } else {
84
+ result.warnings.push('No pagination dots visible');
85
+ if (verbose) console.error(` ⚠ No pagination dots found`);
86
+ }
87
+
88
+ const activeIndex = await getActiveSlideIndex(page, patterns);
89
+ if (activeIndex >= 0) {
90
+ result.tests.push({ name: 'Active slide indicator', passed: true, activeIndex });
91
+ result.passed++;
92
+ result.sliderInfo.currentSlide = activeIndex;
93
+ if (verbose) console.error(` ✓ Active slide: ${activeIndex}`);
94
+ } else {
95
+ result.warnings.push('Could not determine active slide');
96
+ if (verbose) console.error(` ⚠ Could not detect active slide`);
97
+ }
98
+
99
+ if (viewportName === 'desktop' && slideCount > 1) {
100
+ if (verbose) console.error(` Testing autoplay...`);
101
+ const autoplayResult = await checkAutoplay(page, patterns, verbose);
102
+ result.tests.push({
103
+ name: 'Autoplay functionality',
104
+ passed: true,
105
+ note: autoplayResult.hasAutoplay ? undefined : `No autoplay detected (${autoplayResult.changes} changes in ${autoplayResult.duration}ms)`,
106
+ changes: autoplayResult.changes,
107
+ duration: autoplayResult.duration
108
+ });
109
+ result.passed++;
110
+ result.sliderInfo.hasAutoplay = autoplayResult.hasAutoplay;
111
+ if (verbose) console.error(` ${autoplayResult.hasAutoplay ? '✓ Autoplay detected' : 'ℹ No autoplay'} (${autoplayResult.changes} changes)`);
112
+ }
113
+
114
+ return result;
115
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Slider Verification Constants
3
+ *
4
+ * CSS selector patterns for known slider libraries and autoplay detection config.
5
+ * Extracted from verify-slider-helpers.js to keep each file under 200 lines.
6
+ */
7
+
8
+ // Slider library CSS selector patterns
9
+ export const SLIDER_PATTERNS = {
10
+ swiper: {
11
+ container: '[class*="swiper"]',
12
+ slide: '.swiper-slide',
13
+ active: '.swiper-slide-active',
14
+ prev: '.swiper-button-prev',
15
+ next: '.swiper-button-next',
16
+ pagination: '.swiper-pagination'
17
+ },
18
+ slick: {
19
+ container: '[class*="slick"]',
20
+ slide: '.slick-slide',
21
+ active: '.slick-active, .slick-current',
22
+ prev: '.slick-prev',
23
+ next: '.slick-next',
24
+ pagination: '.slick-dots'
25
+ },
26
+ owl: {
27
+ container: '[class*="owl"]',
28
+ slide: '.owl-item',
29
+ active: '.owl-item.active',
30
+ prev: '.owl-prev',
31
+ next: '.owl-next',
32
+ pagination: '.owl-dots'
33
+ },
34
+ splide: {
35
+ container: '.splide',
36
+ slide: '.splide__slide',
37
+ active: '.splide__slide.is-active',
38
+ prev: '.splide__arrow--prev',
39
+ next: '.splide__arrow--next',
40
+ pagination: '.splide__pagination'
41
+ },
42
+ glide: {
43
+ container: '.glide',
44
+ slide: '.glide__slide',
45
+ active: '.glide__slide--active',
46
+ prev: '[data-glide-dir="<"]',
47
+ next: '[data-glide-dir=">"]',
48
+ pagination: '.glide__bullets'
49
+ },
50
+ native: {
51
+ container: '[style*="scroll-snap"], [class*="carousel"], [class*="slider"]',
52
+ slide: '[style*="scroll-snap"] > *, .carousel-item, .slider-item',
53
+ active: '.active, [aria-current="true"]',
54
+ prev: '[class*="prev"], [aria-label*="prev" i]',
55
+ next: '[class*="next"], [aria-label*="next" i]',
56
+ pagination: '[class*="indicator"], [class*="dot"], [role="tablist"]'
57
+ }
58
+ };
59
+
60
+ // Autoplay detection config
61
+ export const AUTOPLAY_CONFIG = {
62
+ waitTime: 6000, // Total wait time in ms
63
+ checkInterval: 1000, // Check every 1s
64
+ requiredChanges: 2 // Require 2 slide changes (per validation)
65
+ };